deno/ext/fs/interface.rs
Daniel Osvaldo Rahmanto e463e57b0e
Some checks are pending
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / build libs (push) Blocked by required conditions
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
fix(ext/node): fs.exists and fs.existsSync compatibility (#30507)
Closes #30506

Other changes:
- Emit deprecation warning.
- Allows
[parallel/test-fs-exists.js](https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-fs-exists.js)
to pass.
- Allows
[test-fs-symlink-dir-junction.js](https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-fs-symlink-dir-junction.js)
to pass. Previously it always fail due to invalid assertion using
`existsSync`.
2025-08-26 15:31:39 +02:00

370 lines
8.8 KiB
Rust

// Copyright 2018-2025 the Deno authors. MIT license.
use core::str;
use std::borrow::Cow;
use std::path::PathBuf;
use std::rc::Rc;
use deno_io::fs::File;
use deno_io::fs::FsResult;
use deno_io::fs::FsStat;
use deno_maybe_sync::MaybeSend;
use deno_maybe_sync::MaybeSync;
use deno_permissions::CheckedPath;
use deno_permissions::CheckedPathBuf;
use serde::Deserialize;
use serde::Serialize;
#[derive(Deserialize, Default, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct OpenOptions {
pub read: bool,
pub write: bool,
pub create: bool,
pub truncate: bool,
pub append: bool,
pub create_new: bool,
pub custom_flags: Option<i32>,
pub mode: Option<u32>,
}
impl OpenOptions {
pub fn read() -> Self {
Self {
read: true,
write: false,
create: false,
truncate: false,
append: false,
create_new: false,
custom_flags: None,
mode: None,
}
}
pub fn write(
create: bool,
append: bool,
create_new: bool,
mode: Option<u32>,
) -> Self {
Self {
read: false,
write: true,
create,
truncate: !append,
append,
create_new,
custom_flags: None,
mode,
}
}
}
#[derive(Deserialize)]
pub enum FsFileType {
#[serde(rename = "file")]
File,
#[serde(rename = "dir")]
Directory,
#[serde(rename = "junction")]
Junction,
}
/// WARNING: This is part of the public JS Deno API.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FsDirEntry {
pub name: String,
pub is_file: bool,
pub is_directory: bool,
pub is_symlink: bool,
}
#[allow(clippy::disallowed_types)]
pub type FileSystemRc = deno_maybe_sync::MaybeArc<dyn FileSystem>;
#[async_trait::async_trait(?Send)]
pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
fn cwd(&self) -> FsResult<PathBuf>;
fn tmp_dir(&self) -> FsResult<PathBuf>;
fn chdir(&self, path: &CheckedPath) -> FsResult<()>;
fn umask(&self, mask: Option<u32>) -> FsResult<u32>;
fn open_sync(
&self,
path: &CheckedPath,
options: OpenOptions,
) -> FsResult<Rc<dyn File>>;
async fn open_async<'a>(
&'a self,
path: CheckedPathBuf,
options: OpenOptions,
) -> FsResult<Rc<dyn File>>;
fn mkdir_sync(
&self,
path: &CheckedPath,
recursive: bool,
mode: Option<u32>,
) -> FsResult<()>;
async fn mkdir_async(
&self,
path: CheckedPathBuf,
recursive: bool,
mode: Option<u32>,
) -> FsResult<()>;
#[cfg(unix)]
fn chmod_sync(&self, path: &CheckedPath, mode: u32) -> FsResult<()>;
#[cfg(not(unix))]
fn chmod_sync(&self, path: &CheckedPath, mode: i32) -> FsResult<()>;
#[cfg(unix)]
async fn chmod_async(&self, path: CheckedPathBuf, mode: u32) -> FsResult<()>;
#[cfg(not(unix))]
async fn chmod_async(&self, path: CheckedPathBuf, mode: i32) -> FsResult<()>;
fn chown_sync(
&self,
path: &CheckedPath,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
async fn chown_async(
&self,
path: CheckedPathBuf,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
fn lchmod_sync(&self, path: &CheckedPath, mode: u32) -> FsResult<()>;
async fn lchmod_async(&self, path: CheckedPathBuf, mode: u32)
-> FsResult<()>;
fn lchown_sync(
&self,
path: &CheckedPath,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
async fn lchown_async(
&self,
path: CheckedPathBuf,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
fn remove_sync(&self, path: &CheckedPath, recursive: bool) -> FsResult<()>;
async fn remove_async(
&self,
path: CheckedPathBuf,
recursive: bool,
) -> FsResult<()>;
fn copy_file_sync(
&self,
oldpath: &CheckedPath,
newpath: &CheckedPath,
) -> FsResult<()>;
async fn copy_file_async(
&self,
oldpath: CheckedPathBuf,
newpath: CheckedPathBuf,
) -> FsResult<()>;
fn cp_sync(&self, path: &CheckedPath, new_path: &CheckedPath)
-> FsResult<()>;
async fn cp_async(
&self,
path: CheckedPathBuf,
new_path: CheckedPathBuf,
) -> FsResult<()>;
fn stat_sync(&self, path: &CheckedPath) -> FsResult<FsStat>;
async fn stat_async(&self, path: CheckedPathBuf) -> FsResult<FsStat>;
fn lstat_sync(&self, path: &CheckedPath) -> FsResult<FsStat>;
async fn lstat_async(&self, path: CheckedPathBuf) -> FsResult<FsStat>;
fn realpath_sync(&self, path: &CheckedPath) -> FsResult<PathBuf>;
async fn realpath_async(&self, path: CheckedPathBuf) -> FsResult<PathBuf>;
fn read_dir_sync(&self, path: &CheckedPath) -> FsResult<Vec<FsDirEntry>>;
async fn read_dir_async(
&self,
path: CheckedPathBuf,
) -> FsResult<Vec<FsDirEntry>>;
fn rename_sync(
&self,
oldpath: &CheckedPath,
newpath: &CheckedPath,
) -> FsResult<()>;
async fn rename_async(
&self,
oldpath: CheckedPathBuf,
newpath: CheckedPathBuf,
) -> FsResult<()>;
fn link_sync(
&self,
oldpath: &CheckedPath,
newpath: &CheckedPath,
) -> FsResult<()>;
async fn link_async(
&self,
oldpath: CheckedPathBuf,
newpath: CheckedPathBuf,
) -> FsResult<()>;
fn symlink_sync(
&self,
oldpath: &CheckedPath,
newpath: &CheckedPath,
file_type: Option<FsFileType>,
) -> FsResult<()>;
async fn symlink_async(
&self,
oldpath: CheckedPathBuf,
newpath: CheckedPathBuf,
file_type: Option<FsFileType>,
) -> FsResult<()>;
fn read_link_sync(&self, path: &CheckedPath) -> FsResult<PathBuf>;
async fn read_link_async(&self, path: CheckedPathBuf) -> FsResult<PathBuf>;
fn truncate_sync(&self, path: &CheckedPath, len: u64) -> FsResult<()>;
async fn truncate_async(
&self,
path: CheckedPathBuf,
len: u64,
) -> FsResult<()>;
fn utime_sync(
&self,
path: &CheckedPath,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn utime_async(
&self,
path: CheckedPathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
fn lutime_sync(
&self,
path: &CheckedPath,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn lutime_async(
&self,
path: CheckedPathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
fn write_file_sync(
&self,
path: &CheckedPath,
options: OpenOptions,
data: &[u8],
) -> FsResult<()> {
let file = self.open_sync(path, options)?;
if let Some(mode) = options.mode {
file.clone().chmod_sync(mode)?;
}
file.write_all_sync(data)?;
Ok(())
}
async fn write_file_async<'a>(
&'a self,
path: CheckedPathBuf,
options: OpenOptions,
data: Vec<u8>,
) -> FsResult<()> {
let file = self.open_async(path, options).await?;
if let Some(mode) = options.mode {
file.clone().chmod_async(mode).await?;
}
file.write_all(data.into()).await?;
Ok(())
}
fn read_file_sync(&self, path: &CheckedPath) -> FsResult<Cow<'static, [u8]>> {
let options = OpenOptions::read();
let file = self.open_sync(path, options)?;
let buf = file.read_all_sync()?;
Ok(buf)
}
async fn read_file_async<'a>(
&'a self,
path: CheckedPathBuf,
) -> FsResult<Cow<'static, [u8]>> {
let options = OpenOptions::read();
let file = self.open_async(path, options).await?;
let buf = file.read_all_async().await?;
Ok(buf)
}
fn is_file_sync(&self, path: &CheckedPath) -> bool {
self.stat_sync(path).map(|m| m.is_file).unwrap_or(false)
}
fn is_dir_sync(&self, path: &CheckedPath) -> bool {
self
.stat_sync(path)
.map(|m| m.is_directory)
.unwrap_or(false)
}
fn exists_sync(&self, path: &CheckedPath) -> bool;
async fn exists_async(&self, path: CheckedPathBuf) -> FsResult<bool>;
fn read_text_file_lossy_sync(
&self,
path: &CheckedPath,
) -> FsResult<Cow<'static, str>> {
let buf = self.read_file_sync(path)?;
Ok(string_from_cow_utf8_lossy(buf))
}
async fn read_text_file_lossy_async<'a>(
&'a self,
path: CheckedPathBuf,
) -> FsResult<Cow<'static, str>> {
let buf = self.read_file_async(path).await?;
Ok(string_from_cow_utf8_lossy(buf))
}
}
#[inline(always)]
fn string_from_cow_utf8_lossy(buf: Cow<'static, [u8]>) -> Cow<'static, str> {
match buf {
Cow::Owned(buf) => Cow::Owned(string_from_utf8_lossy(buf)),
Cow::Borrowed(buf) => String::from_utf8_lossy(buf),
}
}
// Like String::from_utf8_lossy but operates on owned values
#[inline(always)]
fn string_from_utf8_lossy(buf: Vec<u8>) -> String {
match String::from_utf8_lossy(&buf) {
// buf contained non-utf8 chars than have been patched
Cow::Owned(s) => s,
// SAFETY: if Borrowed then the buf only contains utf8 chars,
// we do this instead of .into_owned() to avoid copying the input buf
Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) },
}
}