mirror of
https://github.com/denoland/deno.git
synced 2025-09-21 01:49:51 +00:00

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
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`.
370 lines
8.8 KiB
Rust
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) },
|
|
}
|
|
}
|