// Copyright 2018-2026 the Deno authors. MIT license.

use std::borrow::Cow;
use std::cell::RefCell;
use std::fs::File as StdFile;
use std::future::Future;
use std::io;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::io::FromRawFd;
#[cfg(windows)]
use std::os::windows::io::FromRawHandle;
use std::path::Path;
use std::path::PathBuf;
#[cfg(unix)]
use std::process::Stdio as StdStdio;
use std::rc::Rc;
#[cfg(windows)]
use std::sync::Arc;

use deno_core::AsyncMutFuture;
use deno_core::AsyncRefCell;
use deno_core::AsyncResult;
use deno_core::BufMutView;
use deno_core::BufView;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceHandle;
use deno_core::ResourceHandleFd;
use deno_core::futures::TryFutureExt;
use deno_core::op2;
use deno_core::unsync::TaskQueue;
use deno_core::unsync::spawn_blocking;
use deno_error::JsErrorBox;
#[cfg(windows)]
use deno_subprocess_windows::Stdio as StdStdio;
use fs::FileResource;
use fs::FsError;
use fs::FsResult;
use fs::FsStat;
#[cfg(windows)]
use parking_lot::Condvar;
#[cfg(windows)]
use parking_lot::Mutex;
use tokio::io::AsyncRead;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use tokio::process;
#[cfg(windows)]
use windows_sys::Win32::System::Console::GetStdHandle;
#[cfg(windows)]
use windows_sys::Win32::System::Console::STD_ERROR_HANDLE;
#[cfg(windows)]
use windows_sys::Win32::System::Console::STD_INPUT_HANDLE;
#[cfg(windows)]
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;

mod fd_table;
pub mod fs;
mod pipe;
#[cfg(windows)]
mod winpipe;

mod bi_pipe;

pub use bi_pipe::BiPipe;
pub use bi_pipe::BiPipeRead;
pub use bi_pipe::BiPipeResource;
pub use bi_pipe::BiPipeWrite;
pub use bi_pipe::RawBiPipeHandle;
pub use bi_pipe::bi_pipe_pair_raw;
pub use fd_table::FdTable;
pub use pipe::AsyncPipeRead;
pub use pipe::AsyncPipeWrite;
pub use pipe::PipeRead;
pub use pipe::PipeWrite;
pub use pipe::RawPipeHandle;
pub use pipe::pipe;

/// Abstraction over `AsRawFd` (unix) and `AsRawHandle` (windows)
pub trait AsRawIoHandle {
  fn as_raw_io_handle(&self) -> RawIoHandle;
}

#[cfg(unix)]
impl<T> AsRawIoHandle for T
where
  T: std::os::unix::io::AsRawFd,
{
  fn as_raw_io_handle(&self) -> RawIoHandle {
    self.as_raw_fd()
  }
}

#[cfg(windows)]
impl<T> AsRawIoHandle for T
where
  T: std::os::windows::io::AsRawHandle,
{
  fn as_raw_io_handle(&self) -> RawIoHandle {
    self.as_raw_handle()
  }
}

/// Abstraction over `IntoRawFd` (unix) and `IntoRawHandle` (windows)
pub trait IntoRawIoHandle {
  fn into_raw_io_handle(self) -> RawIoHandle;
}

#[cfg(unix)]
impl<T> IntoRawIoHandle for T
where
  T: std::os::unix::io::IntoRawFd,
{
  fn into_raw_io_handle(self) -> RawIoHandle {
    self.into_raw_fd()
  }
}

#[cfg(windows)]
impl<T> IntoRawIoHandle for T
where
  T: std::os::windows::io::IntoRawHandle,
{
  fn into_raw_io_handle(self) -> RawIoHandle {
    self.into_raw_handle()
  }
}

/// Abstraction over `FromRawFd` (unix) and `FromRawHandle` (windows)
pub trait FromRawIoHandle: Sized {
  /// Constructs a type from a raw io handle (fd/HANDLE).
  ///
  /// # Safety
  ///
  /// Refer to the standard library docs ([unix](https://doc.rust-lang.org/stable/std/os/windows/io/trait.FromRawHandle.html#tymethod.from_raw_handle)) ([windows](https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.html#tymethod.from_raw_fd))
  ///
  unsafe fn from_raw_io_handle(handle: RawIoHandle) -> Self;
}

#[cfg(unix)]
impl<T> FromRawIoHandle for T
where
  T: std::os::unix::io::FromRawFd,
{
  unsafe fn from_raw_io_handle(fd: RawIoHandle) -> T {
    // SAFETY: upheld by caller
    unsafe { T::from_raw_fd(fd) }
  }
}

#[cfg(windows)]
impl<T> FromRawIoHandle for T
where
  T: std::os::windows::io::FromRawHandle,
{
  unsafe fn from_raw_io_handle(fd: RawIoHandle) -> T {
    // SAFETY: upheld by caller
    unsafe { T::from_raw_handle(fd) }
  }
}

#[cfg(unix)]
pub type RawIoHandle = std::os::fd::RawFd;

#[cfg(windows)]
pub type RawIoHandle = std::os::windows::io::RawHandle;

pub fn close_raw_handle(handle: RawIoHandle) {
  #[cfg(unix)]
  {
    // SAFETY: libc call
    unsafe {
      libc::close(handle);
    }
  }
  #[cfg(windows)]
  {
    // SAFETY: win32 call
    unsafe {
      windows_sys::Win32::Foundation::CloseHandle(handle as _);
    }
  }
}

/// Wrap a stdio fd as a StdFile without global statics.
///
/// Uses the raw fd directly. The StdFileResourceInner Drop impl leaks
/// stdio fds (via into_raw_fd) to prevent closing fds 0/1/2 at shutdown.
#[cfg(unix)]
fn stdio_fd(fd: i32) -> StdFile {
  // SAFETY: fd is a valid stdio descriptor (0, 1, or 2).
  unsafe { StdFile::from_raw_fd(fd) }
}

#[cfg(windows)]
fn stdio_fd(fd: i32) -> StdFile {
  let std_handle = match fd {
    0 => STD_INPUT_HANDLE,
    1 => STD_OUTPUT_HANDLE,
    2 => STD_ERROR_HANDLE,
    _ => panic!("Invalid stdio fd {fd}"),
  };
  // SAFETY: GetStdHandle returns a valid handle for the given std device.
  unsafe { StdFile::from_raw_handle(GetStdHandle(std_handle)) }
}

deno_core::extension!(deno_io,
  deps = [ deno_web ],
  lazy_loaded_js = [ "12_io.js" ],
  options = {
    stdio: Option<Stdio>,
  },
  middleware = |op| match op.name {
    "op_print" => op_print(),
    _ => op,
  },
  state = |state, options| {
    let mut fd_table = FdTable::new();

    if let Some(stdio) = options.stdio {
      #[cfg(windows)]
      let stdin_state = {
        let st = Arc::new(Mutex::new(WinTtyState::default()));
        state.put(st.clone());
        st
      };
      #[cfg(unix)]
      let stdin_state = ();

      let t = &mut state.resource_table;

      let stdin_file: Rc<dyn fs::File> = Rc::new(match stdio.stdin.pipe {
        StdioPipeInner::Inherit => StdFileResourceInner::new(
          StdFileResourceKind::Stdin(stdin_state),
          stdio_fd(0),
          None,
        ),
        StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
      });
      fd_table.register(0, stdin_file.clone());
      let rid = t.add(fs::FileResource::new(stdin_file, "stdin".to_string()));
      assert_eq!(rid, 0, "stdin must have ResourceId 0");

      let (stdout_file, child_stdout): (Rc<dyn fs::File>, StdFile) = match stdio.stdout.pipe {
        StdioPipeInner::Inherit => {
          let file = stdio_fd(1);
          // dup for ChildProcessStdio -- it drops normally and must
          // not close the real fd 1.
          let child_handle = file.try_clone().unwrap();
          (
            Rc::new(StdFileResourceInner::new(
              StdFileResourceKind::Stdout,
              file,
              None,
            )),
            child_handle,
          )
        }
        StdioPipeInner::File(pipe) => {
          let child_handle = pipe.try_clone().unwrap();
          (Rc::new(StdFileResourceInner::file(pipe, None)), child_handle)
        }
      };
      fd_table.register(1, stdout_file.clone());
      let rid = t.add(FileResource::new(stdout_file, "stdout".to_string()));
      assert_eq!(rid, 1, "stdout must have ResourceId 1");

      let (stderr_file, child_stderr): (Rc<dyn fs::File>, StdFile) = match stdio.stderr.pipe {
        StdioPipeInner::Inherit => {
          let file = stdio_fd(2);
          let child_handle = file.try_clone().unwrap();
          (
            Rc::new(StdFileResourceInner::new(
              StdFileResourceKind::Stderr,
              file,
              None,
            )),
            child_handle,
          )
        }
        StdioPipeInner::File(pipe) => {
          let child_handle = pipe.try_clone().unwrap();
          (Rc::new(StdFileResourceInner::file(pipe, None)), child_handle)
        }
      };
      fd_table.register(2, stderr_file.clone());
      let rid = t.add(FileResource::new(stderr_file, "stderr".to_string()));
      assert_eq!(rid, 2, "stderr must have ResourceId 2");

      state.put(ChildProcessStdio {
        stdout: child_stdout,
        stderr: child_stderr,
      });
    }

    state.put(fd_table);
  },
);

#[derive(Default)]
pub struct StdioPipe {
  pipe: StdioPipeInner,
}

impl StdioPipe {
  pub const fn inherit() -> Self {
    StdioPipe {
      pipe: StdioPipeInner::Inherit,
    }
  }

  pub fn file(f: impl Into<StdFile>) -> Self {
    StdioPipe {
      pipe: StdioPipeInner::File(f.into()),
    }
  }
}

#[derive(Default)]
enum StdioPipeInner {
  #[default]
  Inherit,
  File(StdFile),
}

impl Clone for StdioPipe {
  fn clone(&self) -> Self {
    match &self.pipe {
      StdioPipeInner::Inherit => Self {
        pipe: StdioPipeInner::Inherit,
      },
      StdioPipeInner::File(pipe) => Self {
        pipe: StdioPipeInner::File(pipe.try_clone().unwrap()),
      },
    }
  }
}

/// Specify how stdin, stdout, and stderr are piped.
/// By default, inherits from the process.
#[derive(Clone, Default)]
pub struct Stdio {
  pub stdin: StdioPipe,
  pub stdout: StdioPipe,
  pub stderr: StdioPipe,
}

/// Holds the effective stdout/stderr handles for child process inheritance.
///
/// When the runtime redirects stdout/stderr (e.g. during `deno test` for
/// output capture), child processes spawned with `stdio: "inherit"` need
/// to inherit the redirected handles, not the original OS stdout/stderr.
/// This struct is stored in `OpState` during IO extension init and read
/// by the process extension when spawning children.
pub struct ChildProcessStdio {
  pub stdout: StdFile,
  pub stderr: StdFile,
}

#[derive(Debug)]
pub struct WriteOnlyResource<S> {
  stream: AsyncRefCell<S>,
}

impl<S: 'static> From<S> for WriteOnlyResource<S> {
  fn from(stream: S) -> Self {
    Self {
      stream: stream.into(),
    }
  }
}

impl<S> WriteOnlyResource<S>
where
  S: AsyncWrite + Unpin + 'static,
{
  pub fn borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<S> {
    RcRef::map(self, |r| &r.stream).borrow_mut()
  }

  async fn write(self: Rc<Self>, data: &[u8]) -> Result<usize, io::Error> {
    let mut stream = self.borrow_mut().await;
    let nwritten = stream.write(data).await?;
    Ok(nwritten)
  }

  async fn shutdown(self: Rc<Self>) -> Result<(), io::Error> {
    let mut stream = self.borrow_mut().await;
    stream.shutdown().await?;
    Ok(())
  }

  pub fn into_inner(self) -> S {
    self.stream.into_inner()
  }
}

#[derive(Debug)]
pub struct ReadOnlyResource<S> {
  stream: AsyncRefCell<S>,
  cancel_handle: CancelHandle,
}

impl<S: 'static> From<S> for ReadOnlyResource<S> {
  fn from(stream: S) -> Self {
    Self {
      stream: stream.into(),
      cancel_handle: Default::default(),
    }
  }
}

impl<S> ReadOnlyResource<S>
where
  S: AsyncRead + Unpin + 'static,
{
  pub fn borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<S> {
    RcRef::map(self, |r| &r.stream).borrow_mut()
  }

  pub fn cancel_handle(self: &Rc<Self>) -> RcRef<CancelHandle> {
    RcRef::map(self, |r| &r.cancel_handle)
  }

  pub fn cancel_read_ops(&self) {
    self.cancel_handle.cancel()
  }

  async fn read(self: Rc<Self>, data: &mut [u8]) -> Result<usize, io::Error> {
    let mut rd = self.borrow_mut().await;
    let nread = rd.read(data).try_or_cancel(self.cancel_handle()).await?;
    Ok(nread)
  }

  pub fn into_inner(self) -> S {
    self.stream.into_inner()
  }
}

pub type ChildStdinResource = WriteOnlyResource<process::ChildStdin>;

impl Resource for ChildStdinResource {
  fn name(&self) -> Cow<'_, str> {
    "childStdin".into()
  }

  deno_core::impl_writable!();

  fn shutdown(self: Rc<Self>) -> AsyncResult<()> {
    Box::pin(self.shutdown().map_err(JsErrorBox::from_err))
  }
}

pub type ChildStdoutResource = ReadOnlyResource<process::ChildStdout>;

impl Resource for ChildStdoutResource {
  deno_core::impl_readable_byob!();

  fn name(&self) -> Cow<'_, str> {
    "childStdout".into()
  }

  fn close(self: Rc<Self>) {
    self.cancel_read_ops();
  }

  // Override the trait's no-op default so `self.cancel_read_ops()` reaches the
  // inherent cancellation rather than `Resource`'s empty default (which the
  // by-value `Rc<Self>` receiver would otherwise resolve to). Without this,
  // closing the resource with a read in flight never cancels it.
  fn cancel_read_ops(self: Rc<Self>) {
    ChildStdoutResource::cancel_read_ops(&self);
  }
}

pub type ChildStderrResource = ReadOnlyResource<process::ChildStderr>;

impl Resource for ChildStderrResource {
  deno_core::impl_readable_byob!();

  fn name(&self) -> Cow<'_, str> {
    "childStderr".into()
  }

  fn close(self: Rc<Self>) {
    self.cancel_read_ops();
  }

  // See the note on ChildStdoutResource::cancel_read_ops.
  fn cancel_read_ops(self: Rc<Self>) {
    ChildStderrResource::cancel_read_ops(&self);
  }
}

#[cfg(windows)]
#[derive(Default)]
pub struct WinTtyState {
  pub cancelled: bool,
  pub reading: bool,
  pub screen_buffer_info:
    Option<windows_sys::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFO>,
  pub cvar: Arc<Condvar>,
}

#[derive(Clone)]
enum StdFileResourceKind {
  File,
  // For stdout and stderr, we sometimes instead use std::io::stdout() directly,
  // because we get some Windows specific functionality for free by using Rust
  // std's wrappers. So we take a bit of a complexity hit in order to not
  // have to duplicate the functionality in Rust's std/src/sys/windows/stdio.rs
  #[cfg(windows)]
  Stdin(Arc<Mutex<WinTtyState>>),
  #[cfg(not(windows))]
  Stdin(()),
  Stdout,
  Stderr,
}

pub struct StdFileResourceInner {
  kind: StdFileResourceKind,
  // We can't use an AsyncRefCell here because we need to allow
  // access to the resource synchronously at any time and
  // asynchronously one at a time in order
  cell: RefCell<Option<StdFile>>,
  // Used to keep async actions in order and only allow one
  // to occur at a time
  cell_async_task_queue: Rc<TaskQueue>,
  handle: ResourceHandleFd,
  maybe_path: Option<PathBuf>,
}

impl Drop for StdFileResourceInner {
  fn drop(&mut self) {
    // For stdio resources, leak the fd to prevent closing fds 0/1/2.
    // These fds must stay open for the lifetime of the process -- without
    // global statics holding them, we rely on this leak to keep them alive.
    match self.kind {
      StdFileResourceKind::Stdin(_)
      | StdFileResourceKind::Stdout
      | StdFileResourceKind::Stderr => {
        if let Some(file) = self.cell.borrow_mut().take() {
          #[cfg(unix)]
          {
            use std::os::unix::io::IntoRawFd;
            let _ = file.into_raw_fd();
          }
          #[cfg(windows)]
          {
            use std::os::windows::io::IntoRawHandle;
            let _ = file.into_raw_handle();
          }
        }
      }
      StdFileResourceKind::File => {
        // Regular files close normally via Drop
      }
    }
  }
}

impl StdFileResourceInner {
  pub fn file(fs_file: StdFile, maybe_path: Option<PathBuf>) -> Self {
    StdFileResourceInner::new(StdFileResourceKind::File, fs_file, maybe_path)
  }

  fn new(
    kind: StdFileResourceKind,
    fs_file: StdFile,
    maybe_path: Option<PathBuf>,
  ) -> Self {
    // We know this will be an fd
    let handle = ResourceHandle::from_fd_like(&fs_file).as_fd_like().unwrap();
    StdFileResourceInner {
      kind,
      handle,
      cell: RefCell::new(Some(fs_file)),
      cell_async_task_queue: Default::default(),
      maybe_path,
    }
  }

  fn with_sync<F, R>(&self, action: F) -> FsResult<R>
  where
    F: FnOnce(&mut StdFile) -> FsResult<R>,
  {
    match self.cell.try_borrow_mut() {
      Ok(mut cell) if cell.is_some() => action(cell.as_mut().unwrap()),
      _ => Err(fs::FsError::FileBusy),
    }
  }

  fn with_inner_blocking_task<F, R: 'static + Send>(
    &self,
    action: F,
  ) -> impl Future<Output = R> + '_
  where
    F: FnOnce(&mut StdFile) -> R + Send + 'static,
  {
    // we want to restrict this to one async action at a time
    let acquire_fut = self.cell_async_task_queue.acquire();
    async move {
      let permit = acquire_fut.await;
      // we take the value out of the cell, use it on a blocking task,
      // then put it back into the cell when we're done
      let mut did_take = false;
      let mut cell_value = {
        let mut cell = self.cell.borrow_mut();
        match cell.as_mut().unwrap().try_clone().ok() {
          Some(value) => value,
          None => {
            did_take = true;
            cell.take().unwrap()
          }
        }
      };
      let (cell_value, result) = spawn_blocking(move || {
        let result = action(&mut cell_value);
        (cell_value, result)
      })
      .await
      .unwrap();

      if did_take {
        // put it back
        self.cell.borrow_mut().replace(cell_value);
      }

      drop(permit); // explicit for clarity
      result
    }
  }

  fn with_blocking_task<F, R: 'static + Send>(
    &self,
    action: F,
  ) -> impl Future<Output = R> + use<F, R>
  where
    F: FnOnce() -> R + Send + 'static,
  {
    // we want to restrict this to one async action at a time
    let acquire_fut = self.cell_async_task_queue.acquire();
    async move {
      let _permit = acquire_fut.await;
      spawn_blocking(action).await.unwrap()
    }
  }

  #[cfg(windows)]
  async fn handle_stdin_read(
    &self,
    state: Arc<Mutex<WinTtyState>>,
    mut buf: BufMutView,
  ) -> FsResult<(usize, BufMutView)> {
    loop {
      let state = state.clone();

      #[allow(
        clippy::result_large_err,
        reason = "error carries the buffer back for reuse"
      )]
      let fut = self.with_inner_blocking_task(move |file| {
        let _terminal_input_guard =
          deno_permissions::prompter::lock_terminal_input();
        /* Start reading, and set the reading flag to true */
        state.lock().reading = true;
        let nread = match file.read(&mut buf) {
          Ok(nread) => nread,
          Err(e) => return Err((e.into(), buf)),
        };

        let mut state = state.lock();
        state.reading = false;

        /* If we canceled the read by sending a VK_RETURN event, restore
        the screen state to undo the visual effect of the VK_RETURN event */
        if state.cancelled {
          if let Some(screen_buffer_info) = state.screen_buffer_info {
            // SAFETY: Win32 calls to open conout$ and restore visual state.
            unsafe {
              let handle = windows_sys::Win32::Storage::FileSystem::CreateFileW(
                "conout$"
                  .encode_utf16()
                  .chain(Some(0))
                  .collect::<Vec<_>>()
                  .as_ptr(),
                windows_sys::Win32::Foundation::GENERIC_READ
                  | windows_sys::Win32::Foundation::GENERIC_WRITE,
                windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ
                  | windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE,
                std::ptr::null(),
                windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING,
                0,
                std::ptr::null_mut(),
              );

              let mut pos = screen_buffer_info.dwCursorPosition;
              /* If the cursor was at the bottom line of the screen buffer, the
              VK_RETURN would have caused the buffer contents to scroll up by
              one line. The right position to reset the cursor to is therefore one
              line higher */
              if pos.Y == screen_buffer_info.dwSize.Y - 1 {
                pos.Y -= 1;
              }

              windows_sys::Win32::System::Console::SetConsoleCursorPosition(
                handle, pos,
              );
              windows_sys::Win32::Foundation::CloseHandle(handle);
            }
          }

          /* Reset the cancelled flag */
          state.cancelled = false;

          /* Unblock the main thread */
          state.cvar.notify_one();

          return Err((FsError::FileBusy, buf));
        }

        Ok((nread, buf))
      });

      match fut.await {
        Err((FsError::FileBusy, b)) => {
          buf = b;
          continue;
        }
        other => return other.map_err(|(e, _)| e),
      }
    }
  }
}

#[async_trait::async_trait(?Send)]
impl crate::fs::File for StdFileResourceInner {
  fn maybe_path(&self) -> Option<&Path> {
    self.maybe_path.as_deref()
  }

  fn write_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<usize> {
    // Rust will line buffer and we don't want that behavior
    // (see https://github.com/denoland/deno/issues/948), so flush stdout and stderr.
    // Although an alternative solution could be to bypass Rust's std by
    // using the raw fds/handles, it will cause encoding issues on Windows
    // that we get solved for free by using Rust's stdio wrappers (see
    // std/src/sys/windows/stdio.rs in Rust's source code).
    match self.kind {
      StdFileResourceKind::File => self.with_sync(|file| Ok(file.write(buf)?)),
      StdFileResourceKind::Stdin(_) => {
        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
      }
      StdFileResourceKind::Stdout => {
        // bypass the file and use std::io::stdout()
        let mut stdout = std::io::stdout().lock();
        let nwritten = stdout.write(buf)?;
        stdout.flush()?;
        Ok(nwritten)
      }
      StdFileResourceKind::Stderr => {
        // bypass the file and use std::io::stderr()
        let mut stderr = std::io::stderr().lock();
        let nwritten = stderr.write(buf)?;
        stderr.flush()?;
        Ok(nwritten)
      }
    }
  }

  fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize> {
    match self.kind {
      StdFileResourceKind::File => self.with_sync(|file| Ok(file.read(buf)?)),
      StdFileResourceKind::Stdin(_) => {
        // Stdin may be set to non-blocking mode by Node's process.stdin
        // (via uv_pipe_open/uv_tty_init which set O_NONBLOCK on the fd).
        // Since O_NONBLOCK is per-file-description, it affects all users
        // of fd 0. Retry on WouldBlock to avoid surfacing EAGAIN to JS.
        self.with_sync(|file| {
          let _terminal_input_guard =
            deno_permissions::prompter::lock_terminal_input();
          loop {
            match file.read(buf) {
              Ok(nread) => return Ok(nread),
              Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                std::thread::yield_now();
                continue;
              }
              Err(e) => return Err(e.into()),
            }
          }
        })
      }
      StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
        Err(FsError::NotSupported)
      }
    }
  }

  fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
    match self.kind {
      StdFileResourceKind::File => {
        self.with_sync(|file| Ok(file.write_all(buf)?))
      }
      StdFileResourceKind::Stdin(_) => {
        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
      }
      StdFileResourceKind::Stdout => {
        // bypass the file and use std::io::stdout()
        let mut stdout = std::io::stdout().lock();
        stdout.write_all(buf)?;
        stdout.flush()?;
        Ok(())
      }
      StdFileResourceKind::Stderr => {
        // bypass the file and use std::io::stderr()
        let mut stderr = std::io::stderr().lock();
        stderr.write_all(buf)?;
        stderr.flush()?;
        Ok(())
      }
    }
  }
  async fn write_all(self: Rc<Self>, buf: BufView) -> FsResult<()> {
    match self.kind {
      StdFileResourceKind::File => {
        self
          .with_inner_blocking_task(move |file| Ok(file.write_all(&buf)?))
          .await
      }
      StdFileResourceKind::Stdin(_) => {
        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
      }
      StdFileResourceKind::Stdout => {
        self
          .with_blocking_task(move || {
            // bypass the file and use std::io::stdout()
            let mut stdout = std::io::stdout().lock();
            stdout.write_all(&buf)?;
            stdout.flush()?;
            Ok(())
          })
          .await
      }
      StdFileResourceKind::Stderr => {
        self
          .with_blocking_task(move || {
            // bypass the file and use std::io::stderr()
            let mut stderr = std::io::stderr().lock();
            stderr.write_all(&buf)?;
            stderr.flush()?;
            Ok(())
          })
          .await
      }
    }
  }

  async fn write(
    self: Rc<Self>,
    view: BufView,
  ) -> FsResult<deno_core::WriteOutcome> {
    match self.kind {
      StdFileResourceKind::File => {
        self
          .with_inner_blocking_task(|file| {
            let nwritten = file.write(&view)?;
            Ok(deno_core::WriteOutcome::Partial { nwritten, view })
          })
          .await
      }
      StdFileResourceKind::Stdin(_) => {
        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
      }
      StdFileResourceKind::Stdout => {
        self
          .with_blocking_task(|| {
            // bypass the file and use std::io::stdout()
            let mut stdout = std::io::stdout().lock();
            let nwritten = stdout.write(&view)?;
            stdout.flush()?;
            Ok(deno_core::WriteOutcome::Partial { nwritten, view })
          })
          .await
      }
      StdFileResourceKind::Stderr => {
        self
          .with_blocking_task(|| {
            // bypass the file and use std::io::stderr()
            let mut stderr = std::io::stderr().lock();
            let nwritten = stderr.write(&view)?;
            stderr.flush()?;
            Ok(deno_core::WriteOutcome::Partial { nwritten, view })
          })
          .await
      }
    }
  }

  fn read_all_sync(self: Rc<Self>) -> FsResult<Cow<'static, [u8]>> {
    match self.kind {
      StdFileResourceKind::File | StdFileResourceKind::Stdin(_) => {
        let mut buf = Vec::new();
        self.with_sync(|file| Ok(file.read_to_end(&mut buf)?))?;
        Ok(Cow::Owned(buf))
      }
      StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
        Err(FsError::NotSupported)
      }
    }
  }
  async fn read_all_async(self: Rc<Self>) -> FsResult<Cow<'static, [u8]>> {
    match self.kind {
      StdFileResourceKind::File | StdFileResourceKind::Stdin(_) => {
        self
          .with_inner_blocking_task(|file| {
            let mut buf = Vec::new();
            file.read_to_end(&mut buf)?;
            Ok(Cow::Owned(buf))
          })
          .await
      }
      StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
        Err(FsError::NotSupported)
      }
    }
  }

  fn chmod_sync(self: Rc<Self>, mode: u32) -> FsResult<()> {
    #[cfg(unix)]
    {
      use std::os::unix::prelude::PermissionsExt;
      self.with_sync(|file| {
        Ok(file.set_permissions(std::fs::Permissions::from_mode(mode))?)
      })
    }
    #[cfg(windows)]
    {
      self.with_sync(|file| {
        let mut permissions = file.metadata()?.permissions();
        if mode & libc::S_IWRITE as u32 > 0 {
          // clippy warning should only be applicable to Unix platforms
          // https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
          #[allow(
            clippy::permissions_set_readonly_false,
            reason = "only applicable to Unix platforms"
          )]
          permissions.set_readonly(false);
        } else {
          permissions.set_readonly(true);
        }
        file.set_permissions(permissions)?;
        Ok(())
      })
    }
  }
  async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()> {
    #[cfg(unix)]
    {
      use std::os::unix::prelude::PermissionsExt;
      self
        .with_inner_blocking_task(move |file| {
          Ok(file.set_permissions(std::fs::Permissions::from_mode(mode))?)
        })
        .await
    }
    #[cfg(windows)]
    {
      self
        .with_inner_blocking_task(move |file| {
          let mut permissions = file.metadata()?.permissions();
          if mode & libc::S_IWRITE as u32 > 0 {
            // clippy warning should only be applicable to Unix platforms
            // https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
            #[allow(
              clippy::permissions_set_readonly_false,
              reason = "only applicable to Unix platforms"
            )]
            permissions.set_readonly(false);
          } else {
            permissions.set_readonly(true);
          }
          file.set_permissions(permissions)?;
          Ok(())
        })
        .await
    }
    #[cfg(not(any(unix, windows)))]
    {
      Err(FsError::NotSupported)
    }
  }

  fn chown_sync(
    self: Rc<Self>,
    _uid: Option<u32>,
    _gid: Option<u32>,
  ) -> FsResult<()> {
    #[cfg(unix)]
    {
      let owner = _uid.map(nix::unistd::Uid::from_raw);
      let group = _gid.map(nix::unistd::Gid::from_raw);
      // SAFETY: self.handle is a valid open file descriptor
      let raw_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(self.handle) };
      let res = nix::unistd::fchown(raw_fd, owner, group);
      if let Err(err) = res {
        Err(io::Error::from_raw_os_error(err as i32).into())
      } else {
        Ok(())
      }
    }
    #[cfg(not(unix))]
    Err(FsError::NotSupported)
  }

  async fn chown_async(
    self: Rc<Self>,
    _uid: Option<u32>,
    _gid: Option<u32>,
  ) -> FsResult<()> {
    #[cfg(unix)]
    {
      self
        .with_inner_blocking_task(move |file| {
          use std::os::fd::AsFd;
          let owner = _uid.map(nix::unistd::Uid::from_raw);
          let group = _gid.map(nix::unistd::Gid::from_raw);
          nix::unistd::fchown(file.as_fd(), owner, group)
            .map_err(|err| io::Error::from_raw_os_error(err as i32).into())
        })
        .await
    }
    #[cfg(not(unix))]
    Err(FsError::NotSupported)
  }

  fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
    self.with_sync(|file| Ok(file.seek(pos)?))
  }
  async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
    self
      .with_inner_blocking_task(move |file| Ok(file.seek(pos)?))
      .await
  }

  fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
    self.with_sync(|file| Ok(file.sync_data()?))
  }
  async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
    self
      .with_inner_blocking_task(|file| Ok(file.sync_data()?))
      .await
  }

  fn sync_sync(self: Rc<Self>) -> FsResult<()> {
    self.with_sync(|file| Ok(file.sync_all()?))
  }
  async fn sync_async(self: Rc<Self>) -> FsResult<()> {
    self
      .with_inner_blocking_task(|file| Ok(file.sync_all()?))
      .await
  }

  fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
    #[cfg(unix)]
    {
      self.with_sync(|file| Ok(file.metadata().map(FsStat::from_std)?))
    }
    #[cfg(windows)]
    {
      self.with_sync(|file| {
        let mut fs_stat = file.metadata().map(FsStat::from_std)?;
        stat_extra(file, &mut fs_stat)?;
        Ok(fs_stat)
      })
    }
    #[cfg(not(any(unix, windows)))]
    {
      Err(FsError::NotSupported)
    }
  }
  async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
    #[cfg(unix)]
    {
      self
        .with_inner_blocking_task(|file| {
          Ok(file.metadata().map(FsStat::from_std)?)
        })
        .await
    }
    #[cfg(windows)]
    {
      self
        .with_inner_blocking_task(|file| {
          let mut fs_stat = file.metadata().map(FsStat::from_std)?;
          stat_extra(file, &mut fs_stat)?;
          Ok(fs_stat)
        })
        .await
    }
    #[cfg(not(any(unix, windows)))]
    {
      Err(FsError::NotSupported)
    }
  }

  fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
    self.with_sync(|file| {
      if exclusive {
        file.lock()?;
      } else {
        file.lock_shared()?;
      }
      Ok(())
    })
  }
  async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
    self
      .with_inner_blocking_task(move |file| {
        if exclusive {
          file.lock()?;
        } else {
          file.lock_shared()?;
        }
        Ok(())
      })
      .await
  }

  fn try_lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<bool> {
    use std::fs::TryLockError;
    self.with_sync(|file| {
      let result = if exclusive {
        file.try_lock()
      } else {
        file.try_lock_shared()
      };
      match result {
        Ok(()) => Ok(true),
        Err(TryLockError::WouldBlock) => Ok(false),
        Err(TryLockError::Error(err)) => Err(err.into()),
      }
    })
  }
  async fn try_lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<bool> {
    use std::fs::TryLockError;
    self
      .with_inner_blocking_task(move |file| {
        let result = if exclusive {
          file.try_lock()
        } else {
          file.try_lock_shared()
        };
        match result {
          Ok(()) => Ok(true),
          Err(TryLockError::WouldBlock) => Ok(false),
          Err(TryLockError::Error(err)) => Err(err.into()),
        }
      })
      .await
  }

  fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
    self.with_sync(|file| Ok(file.unlock()?))
  }
  async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
    self
      .with_inner_blocking_task(|file| Ok(file.unlock()?))
      .await
  }

  fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
    self.with_sync(|file| Ok(file.set_len(len)?))
  }
  async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
    self
      .with_inner_blocking_task(move |file| Ok(file.set_len(len)?))
      .await
  }

  fn utime_sync(
    self: Rc<Self>,
    atime_secs: i64,
    atime_nanos: u32,
    mtime_secs: i64,
    mtime_nanos: u32,
  ) -> FsResult<()> {
    let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
    let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);

    self.with_sync(|file| {
      filetime::set_file_handle_times(file, Some(atime), Some(mtime))?;
      Ok(())
    })
  }
  async fn utime_async(
    self: Rc<Self>,
    atime_secs: i64,
    atime_nanos: u32,
    mtime_secs: i64,
    mtime_nanos: u32,
  ) -> FsResult<()> {
    let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
    let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);

    self
      .with_inner_blocking_task(move |file| {
        filetime::set_file_handle_times(file, Some(atime), Some(mtime))?;
        Ok(())
      })
      .await
  }

  async fn read_byob(
    self: Rc<Self>,
    mut buf: BufMutView,
  ) -> FsResult<(usize, BufMutView)> {
    match &self.kind {
      /* On Windows, we need to handle special read cancellation logic for stdin */
      #[cfg(windows)]
      StdFileResourceKind::Stdin(state) => {
        self.handle_stdin_read(state.clone(), buf).await
      }
      #[cfg(not(windows))]
      StdFileResourceKind::Stdin(_) => {
        // Stdin may be set to non-blocking mode by Node's process.stdin.
        // Retry on WouldBlock (see read_sync comment for details).
        self
          .with_inner_blocking_task(|file| {
            let _terminal_input_guard =
              deno_permissions::prompter::lock_terminal_input();
            loop {
              match file.read(&mut buf) {
                Ok(nread) => return Ok((nread, buf)),
                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                  std::thread::yield_now();
                  continue;
                }
                Err(e) => return Err(e.into()),
              }
            }
          })
          .await
      }
      _ => {
        self
          .with_inner_blocking_task(|file| {
            let nread = file.read(&mut buf)?;
            Ok((nread, buf))
          })
          .await
      }
    }
  }

  fn try_clone_inner(self: Rc<Self>) -> FsResult<Rc<dyn fs::File>> {
    let inner: &Option<_> = &self.cell.borrow();
    match inner {
      Some(inner) => Ok(Rc::new(StdFileResourceInner {
        kind: self.kind.clone(),
        cell: RefCell::new(Some(inner.try_clone()?)),
        cell_async_task_queue: Default::default(),
        handle: self.handle,
        maybe_path: self.maybe_path.clone(),
      })),
      None => Err(FsError::FileBusy),
    }
  }

  fn as_stdio(self: Rc<Self>) -> FsResult<StdStdio> {
    match self.kind {
      StdFileResourceKind::File => self.with_sync(|file| {
        let file = file.try_clone()?;
        Ok(file.into())
      }),
      _ => Ok(StdStdio::inherit()),
    }
  }

  fn read_at_sync(
    self: Rc<Self>,
    buf: &mut [u8],
    position: u64,
  ) -> FsResult<usize> {
    self.with_sync(|file| {
      #[cfg(unix)]
      {
        use std::os::unix::fs::FileExt;
        Ok(file.read_at(buf, position)?)
      }
      #[cfg(windows)]
      {
        // Windows seek_read moves the cursor, so save/restore it.
        use std::io::Seek;
        use std::os::windows::fs::FileExt;
        let current = file.stream_position()?;
        let result = file.seek_read(buf, position);
        file.seek(std::io::SeekFrom::Start(current))?;
        Ok(result?)
      }
    })
  }

  async fn read_at_async(
    self: Rc<Self>,
    mut buf: BufMutView,
    position: u64,
  ) -> FsResult<(usize, BufMutView)> {
    self
      .with_inner_blocking_task(move |file| {
        #[cfg(unix)]
        {
          use std::os::unix::fs::FileExt;
          let nread = file.read_at(&mut buf, position)?;
          Ok((nread, buf))
        }
        #[cfg(windows)]
        {
          use std::io::Seek;
          use std::os::windows::fs::FileExt;
          let current = file.stream_position()?;
          let result = file.seek_read(&mut buf, position);
          file.seek(std::io::SeekFrom::Start(current))?;
          Ok((result?, buf))
        }
      })
      .await
  }

  fn write_at_sync(
    self: Rc<Self>,
    buf: &[u8],
    position: u64,
  ) -> FsResult<usize> {
    self.with_sync(|file| {
      #[cfg(unix)]
      {
        use std::os::unix::fs::FileExt;
        Ok(file.write_at(buf, position)?)
      }
      #[cfg(windows)]
      {
        // Windows seek_write moves the cursor, so save/restore it.
        use std::io::Seek;
        use std::os::windows::fs::FileExt;
        let current = file.stream_position()?;
        let result = file.seek_write(buf, position);
        file.seek(std::io::SeekFrom::Start(current))?;
        Ok(result?)
      }
    })
  }

  fn backing_fd(self: Rc<Self>) -> Option<ResourceHandleFd> {
    Some(self.handle)
  }
}

// override op_print to use the stdout and stderr in the resource table
#[op2(fast)]
pub fn op_print(
  state: &mut OpState,
  #[string] msg: &str,
  is_err: bool,
) -> Result<(), JsErrorBox> {
  let rid = if is_err { 2 } else { 1 };
  FileResource::with_file(state, rid, move |file| {
    match file.write_all_sync(msg.as_bytes()) {
      Err(FsError::Io(io)) if io.kind() == ErrorKind::BrokenPipe => Ok(()),
      other => other,
    }
    .map_err(JsErrorBox::from_err)
  })
}

#[cfg(windows)]
pub fn stat_extra(file: &std::fs::File, fsstat: &mut FsStat) -> FsResult<()> {
  use std::os::windows::io::AsRawHandle;

  unsafe fn get_dev(
    handle: windows_sys::Win32::Foundation::HANDLE,
  ) -> std::io::Result<u64> {
    use windows_sys::Win32::Foundation::FALSE;
    use windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION;
    use windows_sys::Win32::Storage::FileSystem::GetFileInformationByHandle;

    // SAFETY: Win32 calls
    unsafe {
      let info = {
        let mut info =
          std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::zeroed();
        if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE {
          return Err(std::io::Error::last_os_error());
        }

        info.assume_init()
      };

      Ok(info.dwVolumeSerialNumber as u64)
    }
  }

  const WINDOWS_TICK: i64 = 10_000; // 100-nanosecond intervals in a millisecond
  const SEC_TO_UNIX_EPOCH: i64 = 11_644_473_600; // Seconds between Windows epoch and Unix epoch

  fn windows_time_to_unix_time_msec(windows_time: &i64) -> i64 {
    let milliseconds_since_windows_epoch = windows_time / WINDOWS_TICK;
    milliseconds_since_windows_epoch - SEC_TO_UNIX_EPOCH * 1000
  }

  use windows_sys::Wdk::Storage::FileSystem::FILE_ALL_INFORMATION;
  use windows_sys::Win32::Foundation::NTSTATUS;

  unsafe fn query_file_information(
    handle: windows_sys::Win32::Foundation::HANDLE,
  ) -> Result<FILE_ALL_INFORMATION, NTSTATUS> {
    use windows_sys::Wdk::Storage::FileSystem::NtQueryInformationFile;
    use windows_sys::Win32::Foundation::ERROR_MORE_DATA;
    use windows_sys::Win32::Foundation::RtlNtStatusToDosError;
    use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;

    // SAFETY: Win32 calls
    unsafe {
      let mut info = std::mem::MaybeUninit::<FILE_ALL_INFORMATION>::zeroed();
      let mut io_status_block =
        std::mem::MaybeUninit::<IO_STATUS_BLOCK>::zeroed();
      let status = NtQueryInformationFile(
        handle as _,
        io_status_block.as_mut_ptr(),
        info.as_mut_ptr() as *mut _,
        std::mem::size_of::<FILE_ALL_INFORMATION>() as _,
        18, /* FileAllInformation */
      );

      if status < 0 {
        let converted_status = RtlNtStatusToDosError(status);

        // If error more data is returned, then it means that the buffer is too small to get full filename information
        // to have that we should retry. However, since we only use BasicInformation and StandardInformation, it is fine to ignore it
        // since struct is populated with other data anyway.
        // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile#remarksdd
        if converted_status != ERROR_MORE_DATA {
          return Err(converted_status as NTSTATUS);
        }
      }

      Ok(info.assume_init())
    }
  }

  // SAFETY: Win32 calls
  unsafe {
    let file_handle = file.as_raw_handle();

    fsstat.dev = get_dev(file_handle)?;

    if let Ok(file_info) = query_file_information(file_handle) {
      fsstat.ctime = Some(windows_time_to_unix_time_msec(
        &file_info.BasicInformation.ChangeTime,
      ) as u64);

      if file_info.BasicInformation.FileAttributes
        & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT
        != 0
      {
        fsstat.is_symlink = true;
      }

      if file_info.BasicInformation.FileAttributes
        & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY
        != 0
      {
        fsstat.mode |= libc::S_IFDIR as u32;
        fsstat.size = 0;
      } else {
        fsstat.mode |= libc::S_IFREG as u32;
        fsstat.size = file_info.StandardInformation.EndOfFile as u64;
      }

      if file_info.BasicInformation.FileAttributes
        & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_READONLY
        != 0
      {
        fsstat.mode |=
          (libc::S_IREAD | (libc::S_IREAD >> 3) | (libc::S_IREAD >> 6)) as u32;
      } else {
        fsstat.mode |= ((libc::S_IREAD | libc::S_IWRITE)
          | ((libc::S_IREAD | libc::S_IWRITE) >> 3)
          | ((libc::S_IREAD | libc::S_IWRITE) >> 6))
          as u32;
      }

      /* The on-disk allocation size in 512-byte units. */
      fsstat.blocks =
        Some(file_info.StandardInformation.AllocationSize as u64 >> 9);
      fsstat.ino = Some(file_info.InternalInformation.IndexNumber as u64);
      fsstat.nlink = Some(file_info.StandardInformation.NumberOfLinks as u64);
    }

    Ok(())
  }
}
