summaryrefslogtreecommitdiff
path: root/ext/fs/std_fs.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fs/std_fs.rs')
-rw-r--r--ext/fs/std_fs.rs929
1 files changed, 929 insertions, 0 deletions
diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs
new file mode 100644
index 000000000..28c375ff1
--- /dev/null
+++ b/ext/fs/std_fs.rs
@@ -0,0 +1,929 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#![allow(clippy::disallowed_methods)]
+
+use std::fs;
+use std::io;
+use std::io::Read;
+use std::io::Seek;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::time::SystemTime;
+use std::time::UNIX_EPOCH;
+
+use deno_io::StdFileResource;
+use fs3::FileExt;
+
+use crate::interface::FsDirEntry;
+use crate::interface::FsError;
+use crate::interface::FsFileType;
+use crate::interface::FsResult;
+use crate::interface::FsStat;
+use crate::File;
+use crate::FileSystem;
+use crate::OpenOptions;
+
+#[derive(Clone)]
+pub struct StdFs;
+
+#[async_trait::async_trait(?Send)]
+impl FileSystem for StdFs {
+ type File = StdFileResource;
+
+ fn cwd(&self) -> FsResult<PathBuf> {
+ std::env::current_dir().map_err(Into::into)
+ }
+
+ fn tmp_dir(&self) -> FsResult<PathBuf> {
+ Ok(std::env::temp_dir())
+ }
+
+ fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()> {
+ std::env::set_current_dir(path).map_err(Into::into)
+ }
+
+ #[cfg(not(unix))]
+ fn umask(&self, _mask: Option<u32>) -> FsResult<u32> {
+ // TODO implement umask for Windows
+ // see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
+ // and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
+ Err(FsError::NotSupported)
+ }
+
+ #[cfg(unix)]
+ fn umask(&self, mask: Option<u32>) -> FsResult<u32> {
+ use nix::sys::stat::mode_t;
+ use nix::sys::stat::umask;
+ use nix::sys::stat::Mode;
+ let r = if let Some(mask) = mask {
+ // If mask provided, return previous.
+ umask(Mode::from_bits_truncate(mask as mode_t))
+ } else {
+ // If no mask provided, we query the current. Requires two syscalls.
+ let prev = umask(Mode::from_bits_truncate(0o777));
+ let _ = umask(prev);
+ prev
+ };
+ #[cfg(target_os = "linux")]
+ {
+ Ok(r.bits())
+ }
+ #[cfg(target_os = "macos")]
+ {
+ Ok(r.bits() as u32)
+ }
+ }
+
+ fn open_sync(
+ &self,
+ path: impl AsRef<Path>,
+ options: OpenOptions,
+ ) -> FsResult<Self::File> {
+ let opts = open_options(options);
+ let std_file = opts.open(path)?;
+ Ok(StdFileResource::fs_file(std_file))
+ }
+ async fn open_async(
+ &self,
+ path: PathBuf,
+ options: OpenOptions,
+ ) -> FsResult<Self::File> {
+ let opts = open_options(options);
+ let std_file =
+ tokio::task::spawn_blocking(move || opts.open(path)).await??;
+ Ok(StdFileResource::fs_file(std_file))
+ }
+
+ fn mkdir_sync(
+ &self,
+ path: impl AsRef<Path>,
+ recursive: bool,
+ mode: u32,
+ ) -> FsResult<()> {
+ mkdir(path, recursive, mode)
+ }
+ async fn mkdir_async(
+ &self,
+ path: PathBuf,
+ recursive: bool,
+ mode: u32,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || mkdir(path, recursive, mode)).await?
+ }
+
+ fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
+ chmod(path, mode)
+ }
+ async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || chmod(path, mode)).await?
+ }
+
+ fn chown_sync(
+ &self,
+ path: impl AsRef<Path>,
+ uid: Option<u32>,
+ gid: Option<u32>,
+ ) -> FsResult<()> {
+ chown(path, uid, gid)
+ }
+ async fn chown_async(
+ &self,
+ path: PathBuf,
+ uid: Option<u32>,
+ gid: Option<u32>,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || chown(path, uid, gid)).await?
+ }
+
+ fn remove_sync(
+ &self,
+ path: impl AsRef<Path>,
+ recursive: bool,
+ ) -> FsResult<()> {
+ remove(path, recursive)
+ }
+ async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || remove(path, recursive)).await?
+ }
+
+ fn copy_file_sync(
+ &self,
+ from: impl AsRef<Path>,
+ to: impl AsRef<Path>,
+ ) -> FsResult<()> {
+ copy_file(from, to)
+ }
+ async fn copy_file_async(&self, from: PathBuf, to: PathBuf) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || copy_file(from, to)).await?
+ }
+
+ fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
+ stat(path).map(Into::into)
+ }
+ async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat> {
+ tokio::task::spawn_blocking(move || stat(path))
+ .await?
+ .map(Into::into)
+ }
+
+ fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
+ lstat(path).map(Into::into)
+ }
+ async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat> {
+ tokio::task::spawn_blocking(move || lstat(path))
+ .await?
+ .map(Into::into)
+ }
+
+ fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
+ realpath(path)
+ }
+ async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf> {
+ tokio::task::spawn_blocking(move || realpath(path)).await?
+ }
+
+ fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
+ read_dir(path)
+ }
+ async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>> {
+ tokio::task::spawn_blocking(move || read_dir(path)).await?
+ }
+
+ fn rename_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()> {
+ fs::rename(oldpath, newpath).map_err(Into::into)
+ }
+ async fn rename_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || fs::rename(oldpath, newpath))
+ .await?
+ .map_err(Into::into)
+ }
+
+ fn link_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()> {
+ fs::hard_link(oldpath, newpath).map_err(Into::into)
+ }
+ async fn link_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || fs::hard_link(oldpath, newpath))
+ .await?
+ .map_err(Into::into)
+ }
+
+ fn symlink_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ file_type: Option<FsFileType>,
+ ) -> FsResult<()> {
+ symlink(oldpath, newpath, file_type)
+ }
+ async fn symlink_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ file_type: Option<FsFileType>,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || symlink(oldpath, newpath, file_type))
+ .await?
+ }
+
+ fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
+ fs::read_link(path).map_err(Into::into)
+ }
+ async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf> {
+ tokio::task::spawn_blocking(move || fs::read_link(path))
+ .await?
+ .map_err(Into::into)
+ }
+
+ fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()> {
+ truncate(path, len)
+ }
+ async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || truncate(path, len)).await?
+ }
+
+ fn utime_sync(
+ &self,
+ path: impl AsRef<Path>,
+ 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);
+ filetime::set_file_times(path, atime, mtime).map_err(Into::into)
+ }
+ async fn utime_async(
+ &self,
+ path: PathBuf,
+ 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);
+ tokio::task::spawn_blocking(move || {
+ filetime::set_file_times(path, atime, mtime).map_err(Into::into)
+ })
+ .await?
+ }
+
+ fn write_file_sync(
+ &self,
+ path: impl AsRef<Path>,
+ options: OpenOptions,
+ data: &[u8],
+ ) -> FsResult<()> {
+ let opts = open_options(options);
+ let mut file = opts.open(path)?;
+ #[cfg(unix)]
+ if let Some(mode) = options.mode {
+ use std::os::unix::fs::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))?;
+ }
+ file.write_all(data)?;
+ Ok(())
+ }
+
+ async fn write_file_async(
+ &self,
+ path: PathBuf,
+ options: OpenOptions,
+ data: Vec<u8>,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || {
+ let opts = open_options(options);
+ let mut file = opts.open(path)?;
+ #[cfg(unix)]
+ if let Some(mode) = options.mode {
+ use std::os::unix::fs::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))?;
+ }
+ file.write_all(&data)?;
+ Ok(())
+ })
+ .await?
+ }
+
+ fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
+ fs::read(path).map_err(Into::into)
+ }
+ async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
+ tokio::task::spawn_blocking(move || fs::read(path))
+ .await?
+ .map_err(Into::into)
+ }
+}
+
+fn mkdir(path: impl AsRef<Path>, recursive: bool, mode: u32) -> FsResult<()> {
+ let mut builder = fs::DirBuilder::new();
+ builder.recursive(recursive);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::DirBuilderExt;
+ builder.mode(mode);
+ }
+ #[cfg(not(unix))]
+ {
+ _ = mode;
+ }
+ builder.create(path).map_err(Into::into)
+}
+
+#[cfg(unix)]
+fn chmod(path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
+ use std::os::unix::fs::PermissionsExt;
+ let permissions = fs::Permissions::from_mode(mode);
+ fs::set_permissions(path, permissions)?;
+ Ok(())
+}
+
+// TODO: implement chmod for Windows (#4357)
+#[cfg(not(unix))]
+fn chmod(path: impl AsRef<Path>, _mode: u32) -> FsResult<()> {
+ // Still check file/dir exists on Windows
+ std::fs::metadata(path)?;
+ Err(FsError::NotSupported)
+}
+
+#[cfg(unix)]
+fn chown(
+ path: impl AsRef<Path>,
+ uid: Option<u32>,
+ gid: Option<u32>,
+) -> FsResult<()> {
+ use nix::unistd::chown;
+ use nix::unistd::Gid;
+ use nix::unistd::Uid;
+ let owner = uid.map(Uid::from_raw);
+ let group = gid.map(Gid::from_raw);
+ let res = chown(path.as_ref(), owner, group);
+ if let Err(err) = res {
+ return Err(io::Error::from_raw_os_error(err as i32).into());
+ }
+ Ok(())
+}
+
+// TODO: implement chown for Windows
+#[cfg(not(unix))]
+fn chown(
+ _path: impl AsRef<Path>,
+ _uid: Option<u32>,
+ _gid: Option<u32>,
+) -> FsResult<()> {
+ Err(FsError::NotSupported)
+}
+
+fn remove(path: impl AsRef<Path>, recursive: bool) -> FsResult<()> {
+ // TODO: this is racy. This should open fds, and then `unlink` those.
+ let metadata = fs::symlink_metadata(&path)?;
+
+ let file_type = metadata.file_type();
+ let res = if file_type.is_dir() {
+ if recursive {
+ fs::remove_dir_all(&path)
+ } else {
+ fs::remove_dir(&path)
+ }
+ } else if file_type.is_symlink() {
+ #[cfg(unix)]
+ {
+ fs::remove_file(&path)
+ }
+ #[cfg(not(unix))]
+ {
+ use std::os::windows::prelude::MetadataExt;
+ use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
+ if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
+ fs::remove_dir(&path)
+ } else {
+ fs::remove_file(&path)
+ }
+ }
+ } else {
+ fs::remove_file(&path)
+ };
+
+ res.map_err(Into::into)
+}
+
+fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> FsResult<()> {
+ #[cfg(target_os = "macos")]
+ {
+ use libc::clonefile;
+ use libc::stat;
+ use libc::unlink;
+ use std::ffi::CString;
+ use std::os::unix::fs::OpenOptionsExt;
+ use std::os::unix::fs::PermissionsExt;
+ use std::os::unix::prelude::OsStrExt;
+
+ let from_str = CString::new(from.as_ref().as_os_str().as_bytes()).unwrap();
+ let to_str = CString::new(to.as_ref().as_os_str().as_bytes()).unwrap();
+
+ // SAFETY: `from` and `to` are valid C strings.
+ // std::fs::copy does open() + fcopyfile() on macOS. We try to use
+ // clonefile() instead, which is more efficient.
+ unsafe {
+ let mut st = std::mem::zeroed();
+ let ret = stat(from_str.as_ptr(), &mut st);
+ if ret != 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ if st.st_size > 128 * 1024 {
+ // Try unlink. If it fails, we are going to try clonefile() anyway.
+ let _ = unlink(to_str.as_ptr());
+ // Matches rust stdlib behavior for io::copy.
+ // https://github.com/rust-lang/rust/blob/3fdd578d72a24d4efc2fe2ad18eec3b6ba72271e/library/std/src/sys/unix/fs.rs#L1613-L1616
+ if clonefile(from_str.as_ptr(), to_str.as_ptr(), 0) == 0 {
+ return Ok(());
+ }
+ } else {
+ // Do a regular copy. fcopyfile() is an overkill for < 128KB
+ // files.
+ let mut buf = [0u8; 128 * 1024];
+ let mut from_file = fs::File::open(&from)?;
+ let perm = from_file.metadata()?.permissions();
+
+ let mut to_file = fs::OpenOptions::new()
+ // create the file with the correct mode right away
+ .mode(perm.mode())
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&to)?;
+ let writer_metadata = to_file.metadata()?;
+ if writer_metadata.is_file() {
+ // Set the correct file permissions, in case the file already existed.
+ // Don't set the permissions on already existing non-files like
+ // pipes/FIFOs or device nodes.
+ to_file.set_permissions(perm)?;
+ }
+ loop {
+ let nread = from_file.read(&mut buf)?;
+ if nread == 0 {
+ break;
+ }
+ to_file.write_all(&buf[..nread])?;
+ }
+ return Ok(());
+ }
+ }
+
+ // clonefile() failed, fall back to std::fs::copy().
+ }
+
+ fs::copy(from, to)?;
+
+ Ok(())
+}
+
+#[cfg(not(windows))]
+fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::metadata(path)?;
+ Ok(metadata_to_fsstat(metadata))
+}
+
+#[cfg(windows)]
+fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::metadata(path.as_ref())?;
+ let mut fsstat = metadata_to_fsstat(metadata);
+ use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
+ let path = path.as_ref().canonicalize()?;
+ stat_extra(&mut fsstat, &path, FILE_FLAG_BACKUP_SEMANTICS)?;
+ Ok(fsstat)
+}
+
+#[cfg(not(windows))]
+fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::symlink_metadata(path)?;
+ Ok(metadata_to_fsstat(metadata))
+}
+
+#[cfg(windows)]
+fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::symlink_metadata(path.as_ref())?;
+ let mut fsstat = metadata_to_fsstat(metadata);
+ use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
+ use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
+ stat_extra(
+ &mut fsstat,
+ path.as_ref(),
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ )?;
+ Ok(fsstat)
+}
+
+#[cfg(windows)]
+fn stat_extra(
+ fsstat: &mut FsStat,
+ path: &Path,
+ file_flags: winapi::shared::minwindef::DWORD,
+) -> FsResult<()> {
+ use std::os::windows::prelude::OsStrExt;
+
+ use winapi::um::fileapi::CreateFileW;
+ use winapi::um::fileapi::OPEN_EXISTING;
+ use winapi::um::handleapi::CloseHandle;
+ use winapi::um::handleapi::INVALID_HANDLE_VALUE;
+ use winapi::um::winnt::FILE_SHARE_DELETE;
+ use winapi::um::winnt::FILE_SHARE_READ;
+ use winapi::um::winnt::FILE_SHARE_WRITE;
+
+ unsafe fn get_dev(
+ handle: winapi::shared::ntdef::HANDLE,
+ ) -> std::io::Result<u64> {
+ use winapi::shared::minwindef::FALSE;
+ use winapi::um::fileapi::GetFileInformationByHandle;
+ use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION;
+
+ 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)
+ }
+
+ // SAFETY: winapi calls
+ unsafe {
+ let mut path: Vec<_> = path.as_os_str().encode_wide().collect();
+ path.push(0);
+ let file_handle = CreateFileW(
+ path.as_ptr(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ std::ptr::null_mut(),
+ OPEN_EXISTING,
+ file_flags,
+ std::ptr::null_mut(),
+ );
+ if file_handle == INVALID_HANDLE_VALUE {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ let result = get_dev(file_handle);
+ CloseHandle(file_handle);
+ fsstat.dev = result?;
+
+ Ok(())
+ }
+}
+
+#[inline(always)]
+fn metadata_to_fsstat(metadata: fs::Metadata) -> FsStat {
+ 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,
+ }
+ }
+
+ FsStat {
+ 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),
+ }
+}
+
+fn realpath(path: impl AsRef<Path>) -> FsResult<PathBuf> {
+ let canonicalized_path = path.as_ref().canonicalize()?;
+ #[cfg(windows)]
+ let canonicalized_path = PathBuf::from(
+ canonicalized_path
+ .display()
+ .to_string()
+ .trim_start_matches("\\\\?\\"),
+ );
+ Ok(canonicalized_path)
+}
+
+fn read_dir(path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
+ let entries = fs::read_dir(path)?
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ let name = entry.file_name().into_string().ok()?;
+ let metadata = entry.file_type();
+ macro_rules! method_or_false {
+ ($method:ident) => {
+ if let Ok(metadata) = &metadata {
+ metadata.$method()
+ } else {
+ false
+ }
+ };
+ }
+ Some(FsDirEntry {
+ name,
+ is_file: method_or_false!(is_file),
+ is_directory: method_or_false!(is_dir),
+ is_symlink: method_or_false!(is_symlink),
+ })
+ })
+ .collect();
+
+ Ok(entries)
+}
+
+#[cfg(not(windows))]
+fn symlink(
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ _file_type: Option<FsFileType>,
+) -> FsResult<()> {
+ std::os::unix::fs::symlink(oldpath.as_ref(), newpath.as_ref())?;
+ Ok(())
+}
+
+#[cfg(windows)]
+fn symlink(
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ file_type: Option<FsFileType>,
+) -> FsResult<()> {
+ let file_type = match file_type {
+ Some(file_type) => file_type,
+ None => {
+ let old_meta = fs::metadata(&oldpath);
+ match old_meta {
+ Ok(metadata) => {
+ if metadata.is_file() {
+ FsFileType::File
+ } else if metadata.is_dir() {
+ FsFileType::Directory
+ } else {
+ return Err(FsError::Io(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "On Windows the target must be a file or directory",
+ )));
+ }
+ }
+ Err(err) if err.kind() == io::ErrorKind::NotFound => {
+ return Err(FsError::Io(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "On Windows an `options` argument is required if the target does not exist",
+ )))
+ }
+ Err(err) => return Err(err.into()),
+ }
+ }
+ };
+
+ match file_type {
+ FsFileType::File => {
+ std::os::windows::fs::symlink_file(&oldpath, &newpath)?;
+ }
+ FsFileType::Directory => {
+ std::os::windows::fs::symlink_dir(&oldpath, &newpath)?;
+ }
+ };
+
+ Ok(())
+}
+
+fn truncate(path: impl AsRef<Path>, len: u64) -> FsResult<()> {
+ let file = fs::OpenOptions::new().write(true).open(path)?;
+ file.set_len(len)?;
+ Ok(())
+}
+
+fn open_options(options: OpenOptions) -> fs::OpenOptions {
+ let mut open_options = fs::OpenOptions::new();
+ if let Some(mode) = options.mode {
+ // mode only used if creating the file on Unix
+ // if not specified, defaults to 0o666
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::OpenOptionsExt;
+ open_options.mode(mode & 0o777);
+ }
+ #[cfg(not(unix))]
+ let _ = mode; // avoid unused warning
+ }
+ open_options.read(options.read);
+ open_options.create(options.create);
+ open_options.write(options.write);
+ open_options.truncate(options.truncate);
+ open_options.append(options.append);
+ open_options.create_new(options.create_new);
+ open_options
+}
+
+fn sync<T>(
+ resource: Rc<StdFileResource>,
+ f: impl FnOnce(&mut fs::File) -> io::Result<T>,
+) -> FsResult<T> {
+ let res = resource
+ .with_file2(|file| f(file))
+ .ok_or(FsError::FileBusy)??;
+ Ok(res)
+}
+
+async fn nonblocking<T: Send + 'static>(
+ resource: Rc<StdFileResource>,
+ f: impl FnOnce(&mut fs::File) -> io::Result<T> + Send + 'static,
+) -> FsResult<T> {
+ let res = resource.with_file_blocking_task2(f).await?;
+ Ok(res)
+}
+
+#[async_trait::async_trait(?Send)]
+impl File for StdFileResource {
+ fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
+ sync(self, |file| file.write_all(buf))
+ }
+ async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()> {
+ nonblocking(self, move |file| file.write_all(&buf)).await
+ }
+
+ fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>> {
+ sync(self, |file| {
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf)?;
+ Ok(buf)
+ })
+ }
+ async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>> {
+ nonblocking(self, |file| {
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf)?;
+ Ok(buf)
+ })
+ .await
+ }
+
+ fn chmod_sync(self: Rc<Self>, mode: u32) -> FsResult<()> {
+ #[cfg(unix)]
+ {
+ sync(self, |file| {
+ use std::os::unix::prelude::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))
+ })
+ }
+ #[cfg(not(unix))]
+ Err(FsError::NotSupported)
+ }
+
+ async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()> {
+ #[cfg(unix)]
+ {
+ nonblocking(self, move |file| {
+ use std::os::unix::prelude::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))
+ })
+ .await
+ }
+ #[cfg(not(unix))]
+ Err(FsError::NotSupported)
+ }
+
+ fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
+ sync(self, |file| file.seek(pos))
+ }
+ async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
+ nonblocking(self, move |file| file.seek(pos)).await
+ }
+
+ fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
+ sync(self, |file| file.sync_data())
+ }
+ async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
+ nonblocking(self, |file| file.sync_data()).await
+ }
+
+ fn sync_sync(self: Rc<Self>) -> FsResult<()> {
+ sync(self, |file| file.sync_all())
+ }
+ async fn sync_async(self: Rc<Self>) -> FsResult<()> {
+ nonblocking(self, |file| file.sync_all()).await
+ }
+
+ fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
+ sync(self, |file| file.metadata().map(metadata_to_fsstat))
+ }
+ async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
+ nonblocking(self, |file| file.metadata().map(metadata_to_fsstat)).await
+ }
+
+ fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
+ sync(self, |file| {
+ if exclusive {
+ file.lock_exclusive()
+ } else {
+ file.lock_shared()
+ }
+ })
+ }
+ async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
+ nonblocking(self, move |file| {
+ if exclusive {
+ file.lock_exclusive()
+ } else {
+ file.lock_shared()
+ }
+ })
+ .await
+ }
+
+ fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
+ sync(self, |file| file.unlock())
+ }
+ async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
+ nonblocking(self, |file| file.unlock()).await
+ }
+
+ fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
+ sync(self, |file| file.set_len(len))
+ }
+ async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
+ nonblocking(self, move |file| 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);
+ sync(self, |file| {
+ filetime::set_file_handle_times(file, Some(atime), Some(mtime))
+ })
+ }
+ 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);
+ nonblocking(self, move |file| {
+ filetime::set_file_handle_times(file, Some(atime), Some(mtime))
+ })
+ .await
+ }
+}