Skip to main content

deno_io/
lib.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3use std::borrow::Cow;
4use std::cell::RefCell;
5use std::fs::File as StdFile;
6use std::future::Future;
7use std::io;
8use std::io::ErrorKind;
9use std::io::Read;
10use std::io::Seek;
11use std::io::Write;
12#[cfg(unix)]
13use std::os::unix::io::FromRawFd;
14#[cfg(windows)]
15use std::os::windows::io::FromRawHandle;
16use std::path::Path;
17use std::path::PathBuf;
18#[cfg(unix)]
19use std::process::Stdio as StdStdio;
20use std::rc::Rc;
21#[cfg(windows)]
22use std::sync::Arc;
23
24use deno_core::AsyncMutFuture;
25use deno_core::AsyncRefCell;
26use deno_core::AsyncResult;
27use deno_core::BufMutView;
28use deno_core::BufView;
29use deno_core::CancelFuture;
30use deno_core::CancelHandle;
31use deno_core::CancelTryFuture;
32use deno_core::JsBuffer;
33use deno_core::OpState;
34use deno_core::RcRef;
35use deno_core::Resource;
36use deno_core::ResourceHandle;
37use deno_core::ResourceHandleFd;
38use deno_core::futures::TryFutureExt;
39use deno_core::op2;
40use deno_core::unsync::TaskQueue;
41use deno_core::unsync::spawn_blocking;
42use deno_error::JsErrorBox;
43#[cfg(windows)]
44use deno_subprocess_windows::Stdio as StdStdio;
45use fs::FileResource;
46use fs::FsError;
47use fs::FsResult;
48use fs::FsStat;
49use once_cell::sync::Lazy;
50#[cfg(windows)]
51use parking_lot::Condvar;
52#[cfg(windows)]
53use parking_lot::Mutex;
54use tokio::io::AsyncRead;
55use tokio::io::AsyncReadExt;
56use tokio::io::AsyncWrite;
57use tokio::io::AsyncWriteExt;
58use tokio::process;
59#[cfg(windows)]
60use winapi::um::processenv::GetStdHandle;
61#[cfg(windows)]
62use winapi::um::winbase;
63
64pub mod fs;
65mod pipe;
66#[cfg(windows)]
67mod winpipe;
68
69mod bi_pipe;
70
71pub use bi_pipe::BiPipe;
72pub use bi_pipe::BiPipeRead;
73pub use bi_pipe::BiPipeResource;
74pub use bi_pipe::BiPipeWrite;
75pub use bi_pipe::RawBiPipeHandle;
76pub use bi_pipe::bi_pipe_pair_raw;
77pub use pipe::AsyncPipeRead;
78pub use pipe::AsyncPipeWrite;
79pub use pipe::PipeRead;
80pub use pipe::PipeWrite;
81pub use pipe::RawPipeHandle;
82pub use pipe::pipe;
83
84/// Abstraction over `AsRawFd` (unix) and `AsRawHandle` (windows)
85pub trait AsRawIoHandle {
86  fn as_raw_io_handle(&self) -> RawIoHandle;
87}
88
89#[cfg(unix)]
90impl<T> AsRawIoHandle for T
91where
92  T: std::os::unix::io::AsRawFd,
93{
94  fn as_raw_io_handle(&self) -> RawIoHandle {
95    self.as_raw_fd()
96  }
97}
98
99#[cfg(windows)]
100impl<T> AsRawIoHandle for T
101where
102  T: std::os::windows::io::AsRawHandle,
103{
104  fn as_raw_io_handle(&self) -> RawIoHandle {
105    self.as_raw_handle()
106  }
107}
108
109/// Abstraction over `IntoRawFd` (unix) and `IntoRawHandle` (windows)
110pub trait IntoRawIoHandle {
111  fn into_raw_io_handle(self) -> RawIoHandle;
112}
113
114#[cfg(unix)]
115impl<T> IntoRawIoHandle for T
116where
117  T: std::os::unix::io::IntoRawFd,
118{
119  fn into_raw_io_handle(self) -> RawIoHandle {
120    self.into_raw_fd()
121  }
122}
123
124#[cfg(windows)]
125impl<T> IntoRawIoHandle for T
126where
127  T: std::os::windows::io::IntoRawHandle,
128{
129  fn into_raw_io_handle(self) -> RawIoHandle {
130    self.into_raw_handle()
131  }
132}
133
134/// Abstraction over `FromRawFd` (unix) and `FromRawHandle` (windows)
135pub trait FromRawIoHandle: Sized {
136  /// Constructs a type from a raw io handle (fd/HANDLE).
137  ///
138  /// # Safety
139  ///
140  /// 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))
141  ///
142  unsafe fn from_raw_io_handle(handle: RawIoHandle) -> Self;
143}
144
145#[cfg(unix)]
146impl<T> FromRawIoHandle for T
147where
148  T: std::os::unix::io::FromRawFd,
149{
150  unsafe fn from_raw_io_handle(fd: RawIoHandle) -> T {
151    // SAFETY: upheld by caller
152    unsafe { T::from_raw_fd(fd) }
153  }
154}
155
156#[cfg(windows)]
157impl<T> FromRawIoHandle for T
158where
159  T: std::os::windows::io::FromRawHandle,
160{
161  unsafe fn from_raw_io_handle(fd: RawIoHandle) -> T {
162    // SAFETY: upheld by caller
163    unsafe { T::from_raw_handle(fd) }
164  }
165}
166
167#[cfg(unix)]
168pub type RawIoHandle = std::os::fd::RawFd;
169
170#[cfg(windows)]
171pub type RawIoHandle = std::os::windows::io::RawHandle;
172
173pub fn close_raw_handle(handle: RawIoHandle) {
174  #[cfg(unix)]
175  {
176    // SAFETY: libc call
177    unsafe {
178      libc::close(handle);
179    }
180  }
181  #[cfg(windows)]
182  {
183    // SAFETY: win32 call
184    unsafe {
185      windows_sys::Win32::Foundation::CloseHandle(handle as _);
186    }
187  }
188}
189
190// Store the stdio fd/handles in global statics in order to keep them
191// alive for the duration of the application since the last handle/fd
192// being dropped will close the corresponding pipe.
193#[cfg(unix)]
194pub static STDIN_HANDLE: Lazy<StdFile> = Lazy::new(|| {
195  // SAFETY: corresponds to OS stdin
196  unsafe { StdFile::from_raw_fd(0) }
197});
198#[cfg(unix)]
199pub static STDOUT_HANDLE: Lazy<StdFile> = Lazy::new(|| {
200  // SAFETY: corresponds to OS stdout
201  unsafe { StdFile::from_raw_fd(1) }
202});
203#[cfg(unix)]
204pub static STDERR_HANDLE: Lazy<StdFile> = Lazy::new(|| {
205  // SAFETY: corresponds to OS stderr
206  unsafe { StdFile::from_raw_fd(2) }
207});
208
209#[cfg(windows)]
210pub static STDIN_HANDLE: Lazy<StdFile> = Lazy::new(|| {
211  // SAFETY: corresponds to OS stdin
212  unsafe { StdFile::from_raw_handle(GetStdHandle(winbase::STD_INPUT_HANDLE)) }
213});
214#[cfg(windows)]
215pub static STDOUT_HANDLE: Lazy<StdFile> = Lazy::new(|| {
216  // SAFETY: corresponds to OS stdout
217  unsafe { StdFile::from_raw_handle(GetStdHandle(winbase::STD_OUTPUT_HANDLE)) }
218});
219#[cfg(windows)]
220pub static STDERR_HANDLE: Lazy<StdFile> = Lazy::new(|| {
221  // SAFETY: corresponds to OS stderr
222  unsafe { StdFile::from_raw_handle(GetStdHandle(winbase::STD_ERROR_HANDLE)) }
223});
224
225deno_core::extension!(deno_io,
226  deps = [ deno_web ],
227  ops = [
228    op_read_with_cancel_handle,
229    op_read_create_cancel_handle,
230  ],
231  esm = [ "12_io.js" ],
232  options = {
233    stdio: Option<Stdio>,
234  },
235  middleware = |op| match op.name {
236    "op_print" => op_print(),
237    _ => op,
238  },
239  state = |state, options| {
240    if let Some(stdio) = options.stdio {
241      #[cfg(windows)]
242      let stdin_state = {
243        let st = Arc::new(Mutex::new(WinTtyState::default()));
244        state.put(st.clone());
245        st
246      };
247      #[cfg(unix)]
248      let stdin_state = ();
249
250      let t = &mut state.resource_table;
251
252      let rid = t.add(fs::FileResource::new(
253        Rc::new(match stdio.stdin.pipe {
254          StdioPipeInner::Inherit => StdFileResourceInner::new(
255            StdFileResourceKind::Stdin(stdin_state),
256            STDIN_HANDLE.try_clone().unwrap(),
257            None,
258          ),
259          StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
260        }),
261        "stdin".to_string(),
262      ));
263      assert_eq!(rid, 0, "stdin must have ResourceId 0");
264
265      let rid = t.add(FileResource::new(
266        Rc::new(match stdio.stdout.pipe {
267          StdioPipeInner::Inherit => StdFileResourceInner::new(
268            StdFileResourceKind::Stdout,
269            STDOUT_HANDLE.try_clone().unwrap(),
270            None,
271          ),
272          StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
273        }),
274        "stdout".to_string(),
275      ));
276      assert_eq!(rid, 1, "stdout must have ResourceId 1");
277
278      let rid = t.add(FileResource::new(
279        Rc::new(match stdio.stderr.pipe {
280          StdioPipeInner::Inherit => StdFileResourceInner::new(
281            StdFileResourceKind::Stderr,
282            STDERR_HANDLE.try_clone().unwrap(),
283            None,
284          ),
285          StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
286        }),
287        "stderr".to_string(),
288      ));
289      assert_eq!(rid, 2, "stderr must have ResourceId 2");
290    }
291  },
292);
293
294#[derive(Default)]
295pub struct StdioPipe {
296  pipe: StdioPipeInner,
297}
298
299impl StdioPipe {
300  pub const fn inherit() -> Self {
301    StdioPipe {
302      pipe: StdioPipeInner::Inherit,
303    }
304  }
305
306  pub fn file(f: impl Into<StdFile>) -> Self {
307    StdioPipe {
308      pipe: StdioPipeInner::File(f.into()),
309    }
310  }
311}
312
313#[derive(Default)]
314enum StdioPipeInner {
315  #[default]
316  Inherit,
317  File(StdFile),
318}
319
320impl Clone for StdioPipe {
321  fn clone(&self) -> Self {
322    match &self.pipe {
323      StdioPipeInner::Inherit => Self {
324        pipe: StdioPipeInner::Inherit,
325      },
326      StdioPipeInner::File(pipe) => Self {
327        pipe: StdioPipeInner::File(pipe.try_clone().unwrap()),
328      },
329    }
330  }
331}
332
333/// Specify how stdin, stdout, and stderr are piped.
334/// By default, inherits from the process.
335#[derive(Clone, Default)]
336pub struct Stdio {
337  pub stdin: StdioPipe,
338  pub stdout: StdioPipe,
339  pub stderr: StdioPipe,
340}
341
342#[derive(Debug)]
343pub struct WriteOnlyResource<S> {
344  stream: AsyncRefCell<S>,
345}
346
347impl<S: 'static> From<S> for WriteOnlyResource<S> {
348  fn from(stream: S) -> Self {
349    Self {
350      stream: stream.into(),
351    }
352  }
353}
354
355impl<S> WriteOnlyResource<S>
356where
357  S: AsyncWrite + Unpin + 'static,
358{
359  pub fn borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<S> {
360    RcRef::map(self, |r| &r.stream).borrow_mut()
361  }
362
363  async fn write(self: Rc<Self>, data: &[u8]) -> Result<usize, io::Error> {
364    let mut stream = self.borrow_mut().await;
365    let nwritten = stream.write(data).await?;
366    Ok(nwritten)
367  }
368
369  async fn shutdown(self: Rc<Self>) -> Result<(), io::Error> {
370    let mut stream = self.borrow_mut().await;
371    stream.shutdown().await?;
372    Ok(())
373  }
374
375  pub fn into_inner(self) -> S {
376    self.stream.into_inner()
377  }
378}
379
380#[derive(Debug)]
381pub struct ReadOnlyResource<S> {
382  stream: AsyncRefCell<S>,
383  cancel_handle: CancelHandle,
384}
385
386impl<S: 'static> From<S> for ReadOnlyResource<S> {
387  fn from(stream: S) -> Self {
388    Self {
389      stream: stream.into(),
390      cancel_handle: Default::default(),
391    }
392  }
393}
394
395impl<S> ReadOnlyResource<S>
396where
397  S: AsyncRead + Unpin + 'static,
398{
399  pub fn borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<S> {
400    RcRef::map(self, |r| &r.stream).borrow_mut()
401  }
402
403  pub fn cancel_handle(self: &Rc<Self>) -> RcRef<CancelHandle> {
404    RcRef::map(self, |r| &r.cancel_handle)
405  }
406
407  pub fn cancel_read_ops(&self) {
408    self.cancel_handle.cancel()
409  }
410
411  async fn read(self: Rc<Self>, data: &mut [u8]) -> Result<usize, io::Error> {
412    let mut rd = self.borrow_mut().await;
413    let nread = rd.read(data).try_or_cancel(self.cancel_handle()).await?;
414    Ok(nread)
415  }
416
417  pub fn into_inner(self) -> S {
418    self.stream.into_inner()
419  }
420}
421
422pub type ChildStdinResource = WriteOnlyResource<process::ChildStdin>;
423
424impl Resource for ChildStdinResource {
425  fn name(&self) -> Cow<'_, str> {
426    "childStdin".into()
427  }
428
429  deno_core::impl_writable!();
430
431  fn shutdown(self: Rc<Self>) -> AsyncResult<()> {
432    Box::pin(self.shutdown().map_err(JsErrorBox::from_err))
433  }
434}
435
436pub type ChildStdoutResource = ReadOnlyResource<process::ChildStdout>;
437
438impl Resource for ChildStdoutResource {
439  deno_core::impl_readable_byob!();
440
441  fn name(&self) -> Cow<'_, str> {
442    "childStdout".into()
443  }
444
445  fn close(self: Rc<Self>) {
446    self.cancel_read_ops();
447  }
448}
449
450pub type ChildStderrResource = ReadOnlyResource<process::ChildStderr>;
451
452impl Resource for ChildStderrResource {
453  deno_core::impl_readable_byob!();
454
455  fn name(&self) -> Cow<'_, str> {
456    "childStderr".into()
457  }
458
459  fn close(self: Rc<Self>) {
460    self.cancel_read_ops();
461  }
462}
463
464#[cfg(windows)]
465#[derive(Default)]
466pub struct WinTtyState {
467  pub cancelled: bool,
468  pub reading: bool,
469  pub screen_buffer_info:
470    Option<winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO>,
471  pub cvar: Arc<Condvar>,
472}
473
474#[derive(Clone)]
475enum StdFileResourceKind {
476  File,
477  // For stdout and stderr, we sometimes instead use std::io::stdout() directly,
478  // because we get some Windows specific functionality for free by using Rust
479  // std's wrappers. So we take a bit of a complexity hit in order to not
480  // have to duplicate the functionality in Rust's std/src/sys/windows/stdio.rs
481  #[cfg(windows)]
482  Stdin(Arc<Mutex<WinTtyState>>),
483  #[cfg(not(windows))]
484  Stdin(()),
485  Stdout,
486  Stderr,
487}
488
489pub struct StdFileResourceInner {
490  kind: StdFileResourceKind,
491  // We can't use an AsyncRefCell here because we need to allow
492  // access to the resource synchronously at any time and
493  // asynchronously one at a time in order
494  cell: RefCell<Option<StdFile>>,
495  // Used to keep async actions in order and only allow one
496  // to occur at a time
497  cell_async_task_queue: Rc<TaskQueue>,
498  handle: ResourceHandleFd,
499  maybe_path: Option<PathBuf>,
500}
501
502impl StdFileResourceInner {
503  pub fn file(fs_file: StdFile, maybe_path: Option<PathBuf>) -> Self {
504    StdFileResourceInner::new(StdFileResourceKind::File, fs_file, maybe_path)
505  }
506
507  fn new(
508    kind: StdFileResourceKind,
509    fs_file: StdFile,
510    maybe_path: Option<PathBuf>,
511  ) -> Self {
512    // We know this will be an fd
513    let handle = ResourceHandle::from_fd_like(&fs_file).as_fd_like().unwrap();
514    StdFileResourceInner {
515      kind,
516      handle,
517      cell: RefCell::new(Some(fs_file)),
518      cell_async_task_queue: Default::default(),
519      maybe_path,
520    }
521  }
522
523  fn with_sync<F, R>(&self, action: F) -> FsResult<R>
524  where
525    F: FnOnce(&mut StdFile) -> FsResult<R>,
526  {
527    match self.cell.try_borrow_mut() {
528      Ok(mut cell) if cell.is_some() => action(cell.as_mut().unwrap()),
529      _ => Err(fs::FsError::FileBusy),
530    }
531  }
532
533  fn with_inner_blocking_task<F, R: 'static + Send>(
534    &self,
535    action: F,
536  ) -> impl Future<Output = R> + '_
537  where
538    F: FnOnce(&mut StdFile) -> R + Send + 'static,
539  {
540    // we want to restrict this to one async action at a time
541    let acquire_fut = self.cell_async_task_queue.acquire();
542    async move {
543      let permit = acquire_fut.await;
544      // we take the value out of the cell, use it on a blocking task,
545      // then put it back into the cell when we're done
546      let mut did_take = false;
547      let mut cell_value = {
548        let mut cell = self.cell.borrow_mut();
549        match cell.as_mut().unwrap().try_clone().ok() {
550          Some(value) => value,
551          None => {
552            did_take = true;
553            cell.take().unwrap()
554          }
555        }
556      };
557      let (cell_value, result) = spawn_blocking(move || {
558        let result = action(&mut cell_value);
559        (cell_value, result)
560      })
561      .await
562      .unwrap();
563
564      if did_take {
565        // put it back
566        self.cell.borrow_mut().replace(cell_value);
567      }
568
569      drop(permit); // explicit for clarity
570      result
571    }
572  }
573
574  fn with_blocking_task<F, R: 'static + Send>(
575    &self,
576    action: F,
577  ) -> impl Future<Output = R> + use<F, R>
578  where
579    F: FnOnce() -> R + Send + 'static,
580  {
581    // we want to restrict this to one async action at a time
582    let acquire_fut = self.cell_async_task_queue.acquire();
583    async move {
584      let _permit = acquire_fut.await;
585      spawn_blocking(action).await.unwrap()
586    }
587  }
588
589  #[cfg(windows)]
590  async fn handle_stdin_read(
591    &self,
592    state: Arc<Mutex<WinTtyState>>,
593    mut buf: BufMutView,
594  ) -> FsResult<(usize, BufMutView)> {
595    loop {
596      let state = state.clone();
597
598      let fut = self.with_inner_blocking_task(move |file| {
599        /* Start reading, and set the reading flag to true */
600        state.lock().reading = true;
601        let nread = match file.read(&mut buf) {
602          Ok(nread) => nread,
603          Err(e) => return Err((e.into(), buf)),
604        };
605
606        let mut state = state.lock();
607        state.reading = false;
608
609        /* If we canceled the read by sending a VK_RETURN event, restore
610        the screen state to undo the visual effect of the VK_RETURN event */
611        if state.cancelled {
612          if let Some(screen_buffer_info) = state.screen_buffer_info {
613            // SAFETY: WinAPI calls to open conout$ and restore visual state.
614            unsafe {
615              let handle = winapi::um::fileapi::CreateFileW(
616                "conout$"
617                  .encode_utf16()
618                  .chain(Some(0))
619                  .collect::<Vec<_>>()
620                  .as_ptr(),
621                winapi::um::winnt::GENERIC_READ
622                  | winapi::um::winnt::GENERIC_WRITE,
623                winapi::um::winnt::FILE_SHARE_READ
624                  | winapi::um::winnt::FILE_SHARE_WRITE,
625                std::ptr::null_mut(),
626                winapi::um::fileapi::OPEN_EXISTING,
627                0,
628                std::ptr::null_mut(),
629              );
630
631              let mut pos = screen_buffer_info.dwCursorPosition;
632              /* If the cursor was at the bottom line of the screen buffer, the
633              VK_RETURN would have caused the buffer contents to scroll up by
634              one line. The right position to reset the cursor to is therefore one
635              line higher */
636              if pos.Y == screen_buffer_info.dwSize.Y - 1 {
637                pos.Y -= 1;
638              }
639
640              winapi::um::wincon::SetConsoleCursorPosition(handle, pos);
641              winapi::um::handleapi::CloseHandle(handle);
642            }
643          }
644
645          /* Reset the cancelled flag */
646          state.cancelled = false;
647
648          /* Unblock the main thread */
649          state.cvar.notify_one();
650
651          return Err((FsError::FileBusy, buf));
652        }
653
654        Ok((nread, buf))
655      });
656
657      match fut.await {
658        Err((FsError::FileBusy, b)) => {
659          buf = b;
660          continue;
661        }
662        other => return other.map_err(|(e, _)| e),
663      }
664    }
665  }
666}
667
668#[async_trait::async_trait(?Send)]
669impl crate::fs::File for StdFileResourceInner {
670  fn maybe_path(&self) -> Option<&Path> {
671    self.maybe_path.as_deref()
672  }
673
674  fn write_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<usize> {
675    // Rust will line buffer and we don't want that behavior
676    // (see https://github.com/denoland/deno/issues/948), so flush stdout and stderr.
677    // Although an alternative solution could be to bypass Rust's std by
678    // using the raw fds/handles, it will cause encoding issues on Windows
679    // that we get solved for free by using Rust's stdio wrappers (see
680    // std/src/sys/windows/stdio.rs in Rust's source code).
681    match self.kind {
682      StdFileResourceKind::File => self.with_sync(|file| Ok(file.write(buf)?)),
683      StdFileResourceKind::Stdin(_) => {
684        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
685      }
686      StdFileResourceKind::Stdout => {
687        // bypass the file and use std::io::stdout()
688        let mut stdout = std::io::stdout().lock();
689        let nwritten = stdout.write(buf)?;
690        stdout.flush()?;
691        Ok(nwritten)
692      }
693      StdFileResourceKind::Stderr => {
694        // bypass the file and use std::io::stderr()
695        let mut stderr = std::io::stderr().lock();
696        let nwritten = stderr.write(buf)?;
697        stderr.flush()?;
698        Ok(nwritten)
699      }
700    }
701  }
702
703  fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize> {
704    match self.kind {
705      StdFileResourceKind::File | StdFileResourceKind::Stdin(_) => {
706        self.with_sync(|file| Ok(file.read(buf)?))
707      }
708      StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
709        Err(FsError::NotSupported)
710      }
711    }
712  }
713
714  fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
715    match self.kind {
716      StdFileResourceKind::File => {
717        self.with_sync(|file| Ok(file.write_all(buf)?))
718      }
719      StdFileResourceKind::Stdin(_) => {
720        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
721      }
722      StdFileResourceKind::Stdout => {
723        // bypass the file and use std::io::stdout()
724        let mut stdout = std::io::stdout().lock();
725        stdout.write_all(buf)?;
726        stdout.flush()?;
727        Ok(())
728      }
729      StdFileResourceKind::Stderr => {
730        // bypass the file and use std::io::stderr()
731        let mut stderr = std::io::stderr().lock();
732        stderr.write_all(buf)?;
733        stderr.flush()?;
734        Ok(())
735      }
736    }
737  }
738  async fn write_all(self: Rc<Self>, buf: BufView) -> FsResult<()> {
739    match self.kind {
740      StdFileResourceKind::File => {
741        self
742          .with_inner_blocking_task(move |file| Ok(file.write_all(&buf)?))
743          .await
744      }
745      StdFileResourceKind::Stdin(_) => {
746        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
747      }
748      StdFileResourceKind::Stdout => {
749        self
750          .with_blocking_task(move || {
751            // bypass the file and use std::io::stdout()
752            let mut stdout = std::io::stdout().lock();
753            stdout.write_all(&buf)?;
754            stdout.flush()?;
755            Ok(())
756          })
757          .await
758      }
759      StdFileResourceKind::Stderr => {
760        self
761          .with_blocking_task(move || {
762            // bypass the file and use std::io::stderr()
763            let mut stderr = std::io::stderr().lock();
764            stderr.write_all(&buf)?;
765            stderr.flush()?;
766            Ok(())
767          })
768          .await
769      }
770    }
771  }
772
773  async fn write(
774    self: Rc<Self>,
775    view: BufView,
776  ) -> FsResult<deno_core::WriteOutcome> {
777    match self.kind {
778      StdFileResourceKind::File => {
779        self
780          .with_inner_blocking_task(|file| {
781            let nwritten = file.write(&view)?;
782            Ok(deno_core::WriteOutcome::Partial { nwritten, view })
783          })
784          .await
785      }
786      StdFileResourceKind::Stdin(_) => {
787        Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
788      }
789      StdFileResourceKind::Stdout => {
790        self
791          .with_blocking_task(|| {
792            // bypass the file and use std::io::stdout()
793            let mut stdout = std::io::stdout().lock();
794            let nwritten = stdout.write(&view)?;
795            stdout.flush()?;
796            Ok(deno_core::WriteOutcome::Partial { nwritten, view })
797          })
798          .await
799      }
800      StdFileResourceKind::Stderr => {
801        self
802          .with_blocking_task(|| {
803            // bypass the file and use std::io::stderr()
804            let mut stderr = std::io::stderr().lock();
805            let nwritten = stderr.write(&view)?;
806            stderr.flush()?;
807            Ok(deno_core::WriteOutcome::Partial { nwritten, view })
808          })
809          .await
810      }
811    }
812  }
813
814  fn read_all_sync(self: Rc<Self>) -> FsResult<Cow<'static, [u8]>> {
815    match self.kind {
816      StdFileResourceKind::File | StdFileResourceKind::Stdin(_) => {
817        let mut buf = Vec::new();
818        self.with_sync(|file| Ok(file.read_to_end(&mut buf)?))?;
819        Ok(Cow::Owned(buf))
820      }
821      StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
822        Err(FsError::NotSupported)
823      }
824    }
825  }
826  async fn read_all_async(self: Rc<Self>) -> FsResult<Cow<'static, [u8]>> {
827    match self.kind {
828      StdFileResourceKind::File | StdFileResourceKind::Stdin(_) => {
829        self
830          .with_inner_blocking_task(|file| {
831            let mut buf = Vec::new();
832            file.read_to_end(&mut buf)?;
833            Ok(Cow::Owned(buf))
834          })
835          .await
836      }
837      StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
838        Err(FsError::NotSupported)
839      }
840    }
841  }
842
843  fn chmod_sync(self: Rc<Self>, mode: u32) -> FsResult<()> {
844    #[cfg(unix)]
845    {
846      use std::os::unix::prelude::PermissionsExt;
847      self.with_sync(|file| {
848        Ok(file.set_permissions(std::fs::Permissions::from_mode(mode))?)
849      })
850    }
851    #[cfg(windows)]
852    {
853      self.with_sync(|file| {
854        let mut permissions = file.metadata()?.permissions();
855        if mode & libc::S_IWRITE as u32 > 0 {
856          // clippy warning should only be applicable to Unix platforms
857          // https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
858          #[allow(
859            clippy::permissions_set_readonly_false,
860            reason = "only applicable to Unix platforms"
861          )]
862          permissions.set_readonly(false);
863        } else {
864          permissions.set_readonly(true);
865        }
866        file.set_permissions(permissions)?;
867        Ok(())
868      })
869    }
870  }
871  async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()> {
872    #[cfg(unix)]
873    {
874      use std::os::unix::prelude::PermissionsExt;
875      self
876        .with_inner_blocking_task(move |file| {
877          Ok(file.set_permissions(std::fs::Permissions::from_mode(mode))?)
878        })
879        .await
880    }
881    #[cfg(windows)]
882    {
883      self
884        .with_inner_blocking_task(move |file| {
885          let mut permissions = file.metadata()?.permissions();
886          if mode & libc::S_IWRITE as u32 > 0 {
887            // clippy warning should only be applicable to Unix platforms
888            // https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
889            #[allow(
890              clippy::permissions_set_readonly_false,
891              reason = "only applicable to Unix platforms"
892            )]
893            permissions.set_readonly(false);
894          } else {
895            permissions.set_readonly(true);
896          }
897          file.set_permissions(permissions)?;
898          Ok(())
899        })
900        .await
901    }
902    #[cfg(not(any(unix, windows)))]
903    {
904      Err(FsError::NotSupported)
905    }
906  }
907
908  fn chown_sync(
909    self: Rc<Self>,
910    _uid: Option<u32>,
911    _gid: Option<u32>,
912  ) -> FsResult<()> {
913    #[cfg(unix)]
914    {
915      let owner = _uid.map(nix::unistd::Uid::from_raw);
916      let group = _gid.map(nix::unistd::Gid::from_raw);
917      // SAFETY: self.handle is a valid open file descriptor
918      let raw_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(self.handle) };
919      let res = nix::unistd::fchown(raw_fd, owner, group);
920      if let Err(err) = res {
921        Err(io::Error::from_raw_os_error(err as i32).into())
922      } else {
923        Ok(())
924      }
925    }
926    #[cfg(not(unix))]
927    Err(FsError::NotSupported)
928  }
929
930  async fn chown_async(
931    self: Rc<Self>,
932    _uid: Option<u32>,
933    _gid: Option<u32>,
934  ) -> FsResult<()> {
935    #[cfg(unix)]
936    {
937      self
938        .with_inner_blocking_task(move |file| {
939          use std::os::fd::AsFd;
940          let owner = _uid.map(nix::unistd::Uid::from_raw);
941          let group = _gid.map(nix::unistd::Gid::from_raw);
942          nix::unistd::fchown(file.as_fd(), owner, group)
943            .map_err(|err| io::Error::from_raw_os_error(err as i32).into())
944        })
945        .await
946    }
947    #[cfg(not(unix))]
948    Err(FsError::NotSupported)
949  }
950
951  fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
952    self.with_sync(|file| Ok(file.seek(pos)?))
953  }
954  async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
955    self
956      .with_inner_blocking_task(move |file| Ok(file.seek(pos)?))
957      .await
958  }
959
960  fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
961    self.with_sync(|file| Ok(file.sync_data()?))
962  }
963  async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
964    self
965      .with_inner_blocking_task(|file| Ok(file.sync_data()?))
966      .await
967  }
968
969  fn sync_sync(self: Rc<Self>) -> FsResult<()> {
970    self.with_sync(|file| Ok(file.sync_all()?))
971  }
972  async fn sync_async(self: Rc<Self>) -> FsResult<()> {
973    self
974      .with_inner_blocking_task(|file| Ok(file.sync_all()?))
975      .await
976  }
977
978  fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
979    #[cfg(unix)]
980    {
981      self.with_sync(|file| Ok(file.metadata().map(FsStat::from_std)?))
982    }
983    #[cfg(windows)]
984    {
985      self.with_sync(|file| {
986        let mut fs_stat = file.metadata().map(FsStat::from_std)?;
987        stat_extra(file, &mut fs_stat)?;
988        Ok(fs_stat)
989      })
990    }
991    #[cfg(not(any(unix, windows)))]
992    {
993      Err(FsError::NotSupported)
994    }
995  }
996  async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
997    #[cfg(unix)]
998    {
999      self
1000        .with_inner_blocking_task(|file| {
1001          Ok(file.metadata().map(FsStat::from_std)?)
1002        })
1003        .await
1004    }
1005    #[cfg(windows)]
1006    {
1007      self
1008        .with_inner_blocking_task(|file| {
1009          let mut fs_stat = file.metadata().map(FsStat::from_std)?;
1010          stat_extra(file, &mut fs_stat)?;
1011          Ok(fs_stat)
1012        })
1013        .await
1014    }
1015    #[cfg(not(any(unix, windows)))]
1016    {
1017      Err(FsError::NotSupported)
1018    }
1019  }
1020
1021  fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
1022    self.with_sync(|file| {
1023      if exclusive {
1024        file.lock()?;
1025      } else {
1026        file.lock_shared()?;
1027      }
1028      Ok(())
1029    })
1030  }
1031  async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
1032    self
1033      .with_inner_blocking_task(move |file| {
1034        if exclusive {
1035          file.lock()?;
1036        } else {
1037          file.lock_shared()?;
1038        }
1039        Ok(())
1040      })
1041      .await
1042  }
1043
1044  fn try_lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<bool> {
1045    use std::fs::TryLockError;
1046    self.with_sync(|file| {
1047      let result = if exclusive {
1048        file.try_lock()
1049      } else {
1050        file.try_lock_shared()
1051      };
1052      match result {
1053        Ok(()) => Ok(true),
1054        Err(TryLockError::WouldBlock) => Ok(false),
1055        Err(TryLockError::Error(err)) => Err(err.into()),
1056      }
1057    })
1058  }
1059  async fn try_lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<bool> {
1060    use std::fs::TryLockError;
1061    self
1062      .with_inner_blocking_task(move |file| {
1063        let result = if exclusive {
1064          file.try_lock()
1065        } else {
1066          file.try_lock_shared()
1067        };
1068        match result {
1069          Ok(()) => Ok(true),
1070          Err(TryLockError::WouldBlock) => Ok(false),
1071          Err(TryLockError::Error(err)) => Err(err.into()),
1072        }
1073      })
1074      .await
1075  }
1076
1077  fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
1078    self.with_sync(|file| Ok(file.unlock()?))
1079  }
1080  async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
1081    self
1082      .with_inner_blocking_task(|file| Ok(file.unlock()?))
1083      .await
1084  }
1085
1086  fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
1087    self.with_sync(|file| Ok(file.set_len(len)?))
1088  }
1089  async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
1090    self
1091      .with_inner_blocking_task(move |file| Ok(file.set_len(len)?))
1092      .await
1093  }
1094
1095  fn utime_sync(
1096    self: Rc<Self>,
1097    atime_secs: i64,
1098    atime_nanos: u32,
1099    mtime_secs: i64,
1100    mtime_nanos: u32,
1101  ) -> FsResult<()> {
1102    let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
1103    let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
1104
1105    self.with_sync(|file| {
1106      filetime::set_file_handle_times(file, Some(atime), Some(mtime))?;
1107      Ok(())
1108    })
1109  }
1110  async fn utime_async(
1111    self: Rc<Self>,
1112    atime_secs: i64,
1113    atime_nanos: u32,
1114    mtime_secs: i64,
1115    mtime_nanos: u32,
1116  ) -> FsResult<()> {
1117    let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
1118    let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
1119
1120    self
1121      .with_inner_blocking_task(move |file| {
1122        filetime::set_file_handle_times(file, Some(atime), Some(mtime))?;
1123        Ok(())
1124      })
1125      .await
1126  }
1127
1128  async fn read_byob(
1129    self: Rc<Self>,
1130    mut buf: BufMutView,
1131  ) -> FsResult<(usize, BufMutView)> {
1132    match &self.kind {
1133      /* On Windows, we need to handle special read cancellation logic for stdin */
1134      #[cfg(windows)]
1135      StdFileResourceKind::Stdin(state) => {
1136        self.handle_stdin_read(state.clone(), buf).await
1137      }
1138      _ => {
1139        self
1140          .with_inner_blocking_task(|file| {
1141            let nread = file.read(&mut buf)?;
1142            Ok((nread, buf))
1143          })
1144          .await
1145      }
1146    }
1147  }
1148
1149  fn try_clone_inner(self: Rc<Self>) -> FsResult<Rc<dyn fs::File>> {
1150    let inner: &Option<_> = &self.cell.borrow();
1151    match inner {
1152      Some(inner) => Ok(Rc::new(StdFileResourceInner {
1153        kind: self.kind.clone(),
1154        cell: RefCell::new(Some(inner.try_clone()?)),
1155        cell_async_task_queue: Default::default(),
1156        handle: self.handle,
1157        maybe_path: self.maybe_path.clone(),
1158      })),
1159      None => Err(FsError::FileBusy),
1160    }
1161  }
1162
1163  fn as_stdio(self: Rc<Self>) -> FsResult<StdStdio> {
1164    match self.kind {
1165      StdFileResourceKind::File => self.with_sync(|file| {
1166        let file = file.try_clone()?;
1167        Ok(file.into())
1168      }),
1169      _ => Ok(StdStdio::inherit()),
1170    }
1171  }
1172
1173  fn backing_fd(self: Rc<Self>) -> Option<ResourceHandleFd> {
1174    Some(self.handle)
1175  }
1176}
1177
1178pub struct ReadCancelResource(Rc<CancelHandle>);
1179
1180impl Resource for ReadCancelResource {
1181  fn name(&self) -> Cow<'_, str> {
1182    "readCancel".into()
1183  }
1184
1185  fn close(self: Rc<Self>) {
1186    self.0.cancel();
1187  }
1188}
1189
1190#[op2(fast)]
1191#[smi]
1192pub fn op_read_create_cancel_handle(state: &mut OpState) -> u32 {
1193  state
1194    .resource_table
1195    .add(ReadCancelResource(CancelHandle::new_rc()))
1196}
1197
1198#[op2]
1199pub async fn op_read_with_cancel_handle(
1200  state: Rc<RefCell<OpState>>,
1201  #[smi] rid: u32,
1202  #[smi] cancel_handle: u32,
1203  #[buffer] buf: JsBuffer,
1204) -> Result<u32, JsErrorBox> {
1205  let (fut, cancel_rc) = {
1206    let state = state.borrow();
1207    let cancel_handle = state
1208      .resource_table
1209      .get::<ReadCancelResource>(cancel_handle)
1210      .unwrap()
1211      .0
1212      .clone();
1213
1214    (
1215      FileResource::with_file(&state, rid, |file| {
1216        let view = BufMutView::from(buf);
1217        Ok(file.read_byob(view))
1218      }),
1219      cancel_handle,
1220    )
1221  };
1222
1223  fut?
1224    .or_cancel(cancel_rc)
1225    .await
1226    .map_err(|_| JsErrorBox::generic("cancelled"))?
1227    .map(|(n, _)| n as u32)
1228    .map_err(JsErrorBox::from_err)
1229}
1230
1231// override op_print to use the stdout and stderr in the resource table
1232#[op2(fast)]
1233pub fn op_print(
1234  state: &mut OpState,
1235  #[string] msg: &str,
1236  is_err: bool,
1237) -> Result<(), JsErrorBox> {
1238  let rid = if is_err { 2 } else { 1 };
1239  FileResource::with_file(state, rid, move |file| {
1240    match file.write_all_sync(msg.as_bytes()) {
1241      Err(FsError::Io(io)) if io.kind() == ErrorKind::BrokenPipe => Ok(()),
1242      other => other,
1243    }
1244    .map_err(JsErrorBox::from_err)
1245  })
1246}
1247
1248#[cfg(windows)]
1249pub fn stat_extra(file: &std::fs::File, fsstat: &mut FsStat) -> FsResult<()> {
1250  use std::os::windows::io::AsRawHandle;
1251
1252  unsafe fn get_dev(
1253    handle: winapi::shared::ntdef::HANDLE,
1254  ) -> std::io::Result<u64> {
1255    use winapi::shared::minwindef::FALSE;
1256    use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION;
1257    use winapi::um::fileapi::GetFileInformationByHandle;
1258
1259    // SAFETY: winapi calls
1260    unsafe {
1261      let info = {
1262        let mut info =
1263          std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::zeroed();
1264        if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE {
1265          return Err(std::io::Error::last_os_error());
1266        }
1267
1268        info.assume_init()
1269      };
1270
1271      Ok(info.dwVolumeSerialNumber as u64)
1272    }
1273  }
1274
1275  const WINDOWS_TICK: i64 = 10_000; // 100-nanosecond intervals in a millisecond
1276  const SEC_TO_UNIX_EPOCH: i64 = 11_644_473_600; // Seconds between Windows epoch and Unix epoch
1277
1278  fn windows_time_to_unix_time_msec(windows_time: &i64) -> i64 {
1279    let milliseconds_since_windows_epoch = windows_time / WINDOWS_TICK;
1280    milliseconds_since_windows_epoch - SEC_TO_UNIX_EPOCH * 1000
1281  }
1282
1283  use windows_sys::Wdk::Storage::FileSystem::FILE_ALL_INFORMATION;
1284  use windows_sys::Win32::Foundation::NTSTATUS;
1285
1286  unsafe fn query_file_information(
1287    handle: winapi::shared::ntdef::HANDLE,
1288  ) -> Result<FILE_ALL_INFORMATION, NTSTATUS> {
1289    use windows_sys::Wdk::Storage::FileSystem::NtQueryInformationFile;
1290    use windows_sys::Win32::Foundation::ERROR_MORE_DATA;
1291    use windows_sys::Win32::Foundation::RtlNtStatusToDosError;
1292    use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
1293
1294    // SAFETY: winapi calls
1295    unsafe {
1296      let mut info = std::mem::MaybeUninit::<FILE_ALL_INFORMATION>::zeroed();
1297      let mut io_status_block =
1298        std::mem::MaybeUninit::<IO_STATUS_BLOCK>::zeroed();
1299      let status = NtQueryInformationFile(
1300        handle as _,
1301        io_status_block.as_mut_ptr(),
1302        info.as_mut_ptr() as *mut _,
1303        std::mem::size_of::<FILE_ALL_INFORMATION>() as _,
1304        18, /* FileAllInformation */
1305      );
1306
1307      if status < 0 {
1308        let converted_status = RtlNtStatusToDosError(status);
1309
1310        // If error more data is returned, then it means that the buffer is too small to get full filename information
1311        // to have that we should retry. However, since we only use BasicInformation and StandardInformation, it is fine to ignore it
1312        // since struct is populated with other data anyway.
1313        // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile#remarksdd
1314        if converted_status != ERROR_MORE_DATA {
1315          return Err(converted_status as NTSTATUS);
1316        }
1317      }
1318
1319      Ok(info.assume_init())
1320    }
1321  }
1322
1323  // SAFETY: winapi calls
1324  unsafe {
1325    let file_handle = file.as_raw_handle();
1326
1327    fsstat.dev = get_dev(file_handle)?;
1328
1329    if let Ok(file_info) = query_file_information(file_handle) {
1330      fsstat.ctime = Some(windows_time_to_unix_time_msec(
1331        &file_info.BasicInformation.ChangeTime,
1332      ) as u64);
1333
1334      if file_info.BasicInformation.FileAttributes
1335        & winapi::um::winnt::FILE_ATTRIBUTE_REPARSE_POINT
1336        != 0
1337      {
1338        fsstat.is_symlink = true;
1339      }
1340
1341      if file_info.BasicInformation.FileAttributes
1342        & winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY
1343        != 0
1344      {
1345        fsstat.mode |= libc::S_IFDIR as u32;
1346        fsstat.size = 0;
1347      } else {
1348        fsstat.mode |= libc::S_IFREG as u32;
1349        fsstat.size = file_info.StandardInformation.EndOfFile as u64;
1350      }
1351
1352      if file_info.BasicInformation.FileAttributes
1353        & winapi::um::winnt::FILE_ATTRIBUTE_READONLY
1354        != 0
1355      {
1356        fsstat.mode |=
1357          (libc::S_IREAD | (libc::S_IREAD >> 3) | (libc::S_IREAD >> 6)) as u32;
1358      } else {
1359        fsstat.mode |= ((libc::S_IREAD | libc::S_IWRITE)
1360          | ((libc::S_IREAD | libc::S_IWRITE) >> 3)
1361          | ((libc::S_IREAD | libc::S_IWRITE) >> 6))
1362          as u32;
1363      }
1364
1365      /* The on-disk allocation size in 512-byte units. */
1366      fsstat.blocks =
1367        Some(file_info.StandardInformation.AllocationSize as u64 >> 9);
1368      fsstat.ino = Some(file_info.InternalInformation.IndexNumber as u64);
1369      fsstat.nlink = Some(file_info.StandardInformation.NumberOfLinks as u64);
1370    }
1371
1372    Ok(())
1373  }
1374}