summaryrefslogtreecommitdiff
path: root/ext/io/fs.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/io/fs.rs')
-rw-r--r--ext/io/fs.rs330
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()
+ }
+}