// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::borrow::Cow; use std::fmt::Formatter; use std::io; use std::rc::Rc; use std::time::SystemTime; use std::time::UNIX_EPOCH; use deno_core::BufMutView; use deno_core::BufView; use deno_core::OpState; use deno_core::ResourceHandleFd; use deno_core::ResourceId; use tokio::task::JoinError; #[derive(Debug)] pub enum FsError { Io(io::Error), FileBusy, NotSupported, NotCapable(&'static str), } impl std::fmt::Display for FsError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { FsError::Io(err) => std::fmt::Display::fmt(err, f), FsError::FileBusy => f.write_str("file busy"), FsError::NotSupported => f.write_str("not supported"), FsError::NotCapable(err) => { f.write_str(&format!("requires {err} access")) } } } } impl std::error::Error for FsError {} impl FsError { pub fn kind(&self) -> io::ErrorKind { match self { Self::Io(err) => err.kind(), Self::FileBusy => io::ErrorKind::Other, Self::NotSupported => io::ErrorKind::Other, Self::NotCapable(_) => io::ErrorKind::Other, } } pub fn into_io_error(self) -> io::Error { match self { FsError::Io(err) => err, FsError::FileBusy => io::Error::new(self.kind(), "file busy"), FsError::NotSupported => io::Error::new(self.kind(), "not supported"), FsError::NotCapable(err) => { io::Error::new(self.kind(), format!("requires {err} access")) } } } } impl From for FsError { fn from(err: io::Error) -> Self { Self::Io(err) } } impl From for FsError { fn from(err: io::ErrorKind) -> Self { Self::Io(err.into()) } } impl From for FsError { fn from(err: JoinError) -> Self { if err.is_cancelled() { todo!("async tasks must not be cancelled") } if err.is_panic() { std::panic::resume_unwind(err.into_panic()); // resume the panic on the main thread } unreachable!() } } pub type FsResult = Result; pub struct FsStat { pub is_file: bool, pub is_directory: bool, pub is_symlink: bool, pub size: u64, pub mtime: Option, pub atime: Option, pub birthtime: Option, pub dev: u64, pub ino: u64, pub mode: u32, pub nlink: u64, pub uid: u32, pub gid: u32, pub rdev: u64, pub blksize: u64, pub blocks: u64, pub is_block_device: bool, pub is_char_device: bool, pub is_fifo: bool, pub is_socket: bool, } impl FsStat { pub fn from_std(metadata: std::fs::Metadata) -> Self { macro_rules! unix_or_zero { ($member:ident) => {{ #[cfg(unix)] { use std::os::unix::fs::MetadataExt; metadata.$member() } #[cfg(not(unix))] { 0 } }}; } macro_rules! unix_or_false { ($member:ident) => {{ #[cfg(unix)] { use std::os::unix::fs::FileTypeExt; metadata.file_type().$member() } #[cfg(not(unix))] { false } }}; } #[inline(always)] fn to_msec(maybe_time: Result) -> Option { match maybe_time { Ok(time) => Some( time .duration_since(UNIX_EPOCH) .map(|t| t.as_millis() as u64) .unwrap_or_else(|err| err.duration().as_millis() as u64), ), Err(_) => None, } } Self { is_file: metadata.is_file(), is_directory: metadata.is_dir(), is_symlink: metadata.file_type().is_symlink(), size: metadata.len(), mtime: to_msec(metadata.modified()), atime: to_msec(metadata.accessed()), birthtime: to_msec(metadata.created()), dev: unix_or_zero!(dev), ino: unix_or_zero!(ino), mode: unix_or_zero!(mode), nlink: unix_or_zero!(nlink), uid: unix_or_zero!(uid), gid: unix_or_zero!(gid), rdev: unix_or_zero!(rdev), blksize: unix_or_zero!(blksize), blocks: unix_or_zero!(blocks), is_block_device: unix_or_false!(is_block_device), is_char_device: unix_or_false!(is_char_device), is_fifo: unix_or_false!(is_fifo), is_socket: unix_or_false!(is_socket), } } } #[async_trait::async_trait(?Send)] pub trait File { fn read_sync(self: Rc, buf: &mut [u8]) -> FsResult; async fn read(self: Rc, limit: usize) -> FsResult { let buf = BufMutView::new(limit); let (nread, mut buf) = self.read_byob(buf).await?; buf.truncate(nread); Ok(buf.into_view()) } async fn read_byob( self: Rc, buf: BufMutView, ) -> FsResult<(usize, BufMutView)>; fn write_sync(self: Rc, buf: &[u8]) -> FsResult; async fn write( self: Rc, buf: BufView, ) -> FsResult; fn write_all_sync(self: Rc, buf: &[u8]) -> FsResult<()>; async fn write_all(self: Rc, buf: BufView) -> FsResult<()>; fn read_all_sync(self: Rc) -> FsResult>; async fn read_all_async(self: Rc) -> FsResult>; fn chmod_sync(self: Rc, pathmode: u32) -> FsResult<()>; async fn chmod_async(self: Rc, mode: u32) -> FsResult<()>; fn seek_sync(self: Rc, pos: io::SeekFrom) -> FsResult; async fn seek_async(self: Rc, pos: io::SeekFrom) -> FsResult; fn datasync_sync(self: Rc) -> FsResult<()>; async fn datasync_async(self: Rc) -> FsResult<()>; fn sync_sync(self: Rc) -> FsResult<()>; async fn sync_async(self: Rc) -> FsResult<()>; fn stat_sync(self: Rc) -> FsResult; async fn stat_async(self: Rc) -> FsResult; fn lock_sync(self: Rc, exclusive: bool) -> FsResult<()>; async fn lock_async(self: Rc, exclusive: bool) -> FsResult<()>; fn unlock_sync(self: Rc) -> FsResult<()>; async fn unlock_async(self: Rc) -> FsResult<()>; fn truncate_sync(self: Rc, len: u64) -> FsResult<()>; async fn truncate_async(self: Rc, len: u64) -> FsResult<()>; fn utime_sync( self: Rc, atime_secs: i64, atime_nanos: u32, mtime_secs: i64, mtime_nanos: u32, ) -> FsResult<()>; async fn utime_async( self: Rc, atime_secs: i64, atime_nanos: u32, mtime_secs: i64, mtime_nanos: u32, ) -> FsResult<()>; // lower level functionality fn as_stdio(self: Rc) -> FsResult; fn backing_fd(self: Rc) -> Option; fn try_clone_inner(self: Rc) -> FsResult>; } pub struct FileResource { name: String, file: Rc, } impl FileResource { pub fn new(file: Rc, name: String) -> Self { Self { name, file } } fn with_resource( state: &OpState, rid: ResourceId, f: F, ) -> Result where F: FnOnce(Rc) -> Result, { let resource = state.resource_table.get::(rid)?; f(resource) } pub fn get_file( state: &OpState, rid: ResourceId, ) -> Result, deno_core::error::AnyError> { let resource = state.resource_table.get::(rid)?; Ok(resource.file()) } pub fn with_file( state: &OpState, rid: ResourceId, f: F, ) -> Result where F: FnOnce(Rc) -> Result, { Self::with_resource(state, rid, |r| f(r.file.clone())) } pub fn file(&self) -> Rc { self.file.clone() } } impl deno_core::Resource for FileResource { fn name(&self) -> Cow { Cow::Borrowed(&self.name) } fn read(self: Rc, limit: usize) -> deno_core::AsyncResult { Box::pin(async move { self .file .clone() .read(limit) .await .map_err(|err| err.into()) }) } fn read_byob( self: Rc, buf: BufMutView, ) -> deno_core::AsyncResult<(usize, BufMutView)> { Box::pin(async move { self .file .clone() .read_byob(buf) .await .map_err(|err| err.into()) }) } fn write( self: Rc, buf: BufView, ) -> deno_core::AsyncResult { Box::pin(async move { self.file.clone().write(buf).await.map_err(|err| err.into()) }) } fn write_all(self: Rc, buf: BufView) -> deno_core::AsyncResult<()> { Box::pin(async move { self .file .clone() .write_all(buf) .await .map_err(|err| err.into()) }) } fn read_byob_sync( self: Rc, data: &mut [u8], ) -> Result { self.file.clone().read_sync(data).map_err(|err| err.into()) } fn write_sync( self: Rc, data: &[u8], ) -> Result { self.file.clone().write_sync(data).map_err(|err| err.into()) } fn backing_fd(self: Rc) -> Option { self.file.clone().backing_fd() } }