diff options
Diffstat (limited to 'ext/io/fs.rs')
-rw-r--r-- | ext/io/fs.rs | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/ext/io/fs.rs b/ext/io/fs.rs new file mode 100644 index 000000000..bb6bdec4f --- /dev/null +++ b/ext/io/fs.rs @@ -0,0 +1,330 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::borrow::Cow; +use std::io; +use std::rc::Rc; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + +use deno_core::error::not_supported; +use deno_core::error::resource_unavailable; +use deno_core::error::AnyError; +use deno_core::BufMutView; +use deno_core::BufView; +use deno_core::OpState; +use deno_core::ResourceId; +use tokio::task::JoinError; + +pub enum FsError { + Io(io::Error), + FileBusy, + NotSupported, +} + +impl From<io::Error> for FsError { + fn from(err: io::Error) -> Self { + Self::Io(err) + } +} + +impl From<FsError> for AnyError { + fn from(err: FsError) -> Self { + match err { + FsError::Io(err) => AnyError::from(err), + FsError::FileBusy => resource_unavailable(), + FsError::NotSupported => not_supported(), + } + } +} + +impl From<JoinError> 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<T> = Result<T, FsError>; + +pub struct FsStat { + pub is_file: bool, + pub is_directory: bool, + pub is_symlink: bool, + pub size: u64, + + pub mtime: Option<u64>, + pub atime: Option<u64>, + pub birthtime: Option<u64>, + + 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, +} + +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 + } + }}; + } + + #[inline(always)] + fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Option<u64> { + 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), + } + } +} + +#[async_trait::async_trait(?Send)] +pub trait File { + fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize>; + async fn read(self: Rc<Self>, limit: usize) -> FsResult<BufView> { + let vec = vec![0; limit]; + let buf = BufMutView::from(vec); + let (nread, buf) = self.read_byob(buf).await?; + let mut vec = buf.unwrap_vec(); + if vec.len() != nread { + vec.truncate(nread); + } + Ok(BufView::from(vec)) + } + async fn read_byob( + self: Rc<Self>, + buf: BufMutView, + ) -> FsResult<(usize, BufMutView)>; + + fn write_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<usize>; + async fn write( + self: Rc<Self>, + buf: BufView, + ) -> FsResult<deno_core::WriteOutcome>; + + fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()>; + async fn write_all(self: Rc<Self>, buf: BufView) -> FsResult<()>; + + fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>>; + async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>>; + + fn chmod_sync(self: Rc<Self>, pathmode: u32) -> FsResult<()>; + async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()>; + + fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>; + async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>; + + fn datasync_sync(self: Rc<Self>) -> FsResult<()>; + async fn datasync_async(self: Rc<Self>) -> FsResult<()>; + + fn sync_sync(self: Rc<Self>) -> FsResult<()>; + async fn sync_async(self: Rc<Self>) -> FsResult<()>; + + fn stat_sync(self: Rc<Self>) -> FsResult<FsStat>; + async fn stat_async(self: Rc<Self>) -> FsResult<FsStat>; + + fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()>; + async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()>; + + fn unlock_sync(self: Rc<Self>) -> FsResult<()>; + async fn unlock_async(self: Rc<Self>) -> FsResult<()>; + + fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()>; + async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()>; + + fn utime_sync( + self: Rc<Self>, + atime_secs: i64, + atime_nanos: u32, + mtime_secs: i64, + mtime_nanos: u32, + ) -> FsResult<()>; + async fn utime_async( + self: Rc<Self>, + atime_secs: i64, + atime_nanos: u32, + mtime_secs: i64, + mtime_nanos: u32, + ) -> FsResult<()>; + + // lower level functionality + fn as_stdio(self: Rc<Self>) -> FsResult<std::process::Stdio>; + #[cfg(unix)] + fn backing_fd(self: Rc<Self>) -> Option<std::os::unix::prelude::RawFd>; + #[cfg(windows)] + fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle>; + fn try_clone_inner(self: Rc<Self>) -> FsResult<Rc<dyn File>>; +} + +pub struct FileResource { + name: String, + file: Rc<dyn File>, +} + +impl FileResource { + pub fn new(file: Rc<dyn File>, name: String) -> Self { + Self { name, file } + } + + pub fn with_resource<F, R>( + state: &OpState, + rid: ResourceId, + f: F, + ) -> Result<R, AnyError> + where + F: FnOnce(Rc<FileResource>) -> Result<R, AnyError>, + { + let resource = state.resource_table.get::<FileResource>(rid)?; + f(resource) + } + + pub fn get_file( + state: &OpState, + rid: ResourceId, + ) -> Result<Rc<dyn File>, AnyError> { + let resource = state.resource_table.get::<FileResource>(rid)?; + Ok(resource.file()) + } + + pub fn with_file<F, R>( + state: &OpState, + rid: ResourceId, + f: F, + ) -> Result<R, AnyError> + where + F: FnOnce(Rc<dyn File>) -> Result<R, AnyError>, + { + Self::with_resource(state, rid, |r| f(r.file.clone())) + } + + pub fn file(&self) -> Rc<dyn File> { + self.file.clone() + } +} + +impl deno_core::Resource for FileResource { + fn name(&self) -> Cow<str> { + Cow::Borrowed(&self.name) + } + + fn read( + self: Rc<Self>, + limit: usize, + ) -> deno_core::AsyncResult<deno_core::BufView> { + Box::pin(async move { + self + .file + .clone() + .read(limit) + .await + .map_err(|err| err.into()) + }) + } + + fn read_byob( + self: Rc<Self>, + buf: deno_core::BufMutView, + ) -> deno_core::AsyncResult<(usize, deno_core::BufMutView)> { + Box::pin(async move { + self + .file + .clone() + .read_byob(buf) + .await + .map_err(|err| err.into()) + }) + } + + fn write( + self: Rc<Self>, + buf: deno_core::BufView, + ) -> deno_core::AsyncResult<deno_core::WriteOutcome> { + Box::pin(async move { + self.file.clone().write(buf).await.map_err(|err| err.into()) + }) + } + + fn write_all( + self: Rc<Self>, + buf: deno_core::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<Self>, + data: &mut [u8], + ) -> Result<usize, deno_core::anyhow::Error> { + self.file.clone().read_sync(data).map_err(|err| err.into()) + } + + fn write_sync( + self: Rc<Self>, + data: &[u8], + ) -> Result<usize, deno_core::anyhow::Error> { + self.file.clone().write_sync(data).map_err(|err| err.into()) + } + + #[cfg(unix)] + fn backing_fd(self: Rc<Self>) -> Option<std::os::unix::prelude::RawFd> { + self.file.clone().backing_fd() + } + + #[cfg(windows)] + fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle> { + self.file.clone().backing_fd() + } +} |