Codex Handbook
exec-server/src/local_file_system.rs 954 lines
use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_path_uri::PathUri;use std::path::Path;use std::path::PathBuf;use std::sync::Arc;use std::sync::LazyLock;use std::time::SystemTime;use std::time::UNIX_EPOCH;use tokio::io;use tokio::io::AsyncReadExt;use tokio_util::io::ReaderStream;use crate::CopyOptions;use crate::CreateDirectoryOptions;use crate::ExecServerRuntimePaths;use crate::ExecutorFileSystem;use crate::ExecutorFileSystemFuture;use crate::FILE_READ_CHUNK_SIZE;use crate::FileMetadata;use crate::FileSystemReadStream;use crate::FileSystemResult;use crate::FileSystemSandboxContext;use crate::ReadDirectoryEntry;use crate::RemoveOptions;use crate::regular_file;use crate::sandboxed_file_system::SandboxedFileSystem;const MAX_READ_FILE_BYTES: u64 = 512 * 1024 * 1024;fn file_too_large_error() -> io::Error {    io::Error::new(        io::ErrorKind::InvalidInput,        format!("file is too large to read: limit is {MAX_READ_FILE_BYTES} bytes"),    )}pub static LOCAL_FS: LazyLock<Arc<dyn ExecutorFileSystem>> =    LazyLock::new(|| -> Arc<dyn ExecutorFileSystem> { Arc::new(LocalFileSystem::unsandboxed()) });#[derive(Clone, Default)]pub(crate) struct DirectFileSystem;#[derive(Clone, Default)]pub(crate) struct UnsandboxedFileSystem {    file_system: DirectFileSystem,}#[derive(Clone, Default)]pub struct LocalFileSystem {    unsandboxed: UnsandboxedFileSystem,    sandboxed: Option<SandboxedFileSystem>,}impl LocalFileSystem {    pub fn unsandboxed() -> Self {        Self {            unsandboxed: UnsandboxedFileSystem::default(),            sandboxed: None,        }    }    pub fn with_runtime_paths(runtime_paths: ExecServerRuntimePaths) -> Self {        Self {            unsandboxed: UnsandboxedFileSystem::default(),            sandboxed: Some(SandboxedFileSystem::new(runtime_paths)),        }    }    fn sandboxed(&self) -> io::Result<&SandboxedFileSystem> {        self.sandboxed.as_ref().ok_or_else(|| {            io::Error::new(                io::ErrorKind::InvalidInput,                "sandboxed filesystem operations require configured runtime paths",            )        })    }    fn file_system_for<'a>(        &'a self,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> io::Result<(        &'a dyn ExecutorFileSystem,        Option<&'a FileSystemSandboxContext>,    )> {        if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox) {            Ok((self.sandboxed()?, sandbox))        } else {            Ok((&self.unsandboxed, sandbox))        }    }}impl LocalFileSystem {    pub(crate) async fn open_file_for_read(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<tokio::fs::File> {        if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox) {            return Err(io::Error::new(                io::ErrorKind::InvalidInput,                "streaming file reads do not support platform sandboxing",            ));        }        self.unsandboxed.open_file_for_read(path, sandbox).await    }    async fn canonicalize(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<PathUri> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.canonicalize(path, sandbox).await    }    async fn read_file(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<Vec<u8>> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.read_file(path, sandbox).await    }    async fn read_file_stream(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<FileSystemReadStream> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.read_file_stream(path, sandbox).await    }    async fn write_file(        &self,        path: &PathUri,        contents: Vec<u8>,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.write_file(path, contents, sandbox).await    }    async fn create_directory(        &self,        path: &PathUri,        options: CreateDirectoryOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.create_directory(path, options, sandbox).await    }    async fn get_metadata(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<FileMetadata> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.get_metadata(path, sandbox).await    }    async fn read_directory(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<Vec<ReadDirectoryEntry>> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.read_directory(path, sandbox).await    }    async fn remove(        &self,        path: &PathUri,        options: RemoveOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system.remove(path, options, sandbox).await    }    async fn copy(        &self,        source_path: &PathUri,        destination_path: &PathUri,        options: CopyOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        let (file_system, sandbox) = self.file_system_for(sandbox)?;        file_system            .copy(source_path, destination_path, options, sandbox)            .await    }}impl ExecutorFileSystem for LocalFileSystem {    fn canonicalize<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, PathUri> {        Box::pin(LocalFileSystem::canonicalize(self, path, sandbox))    }    fn read_file<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<u8>> {        Box::pin(LocalFileSystem::read_file(self, path, sandbox))    }    fn read_file_stream<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream> {        Box::pin(LocalFileSystem::read_file_stream(self, path, sandbox))    }    fn write_file<'a>(        &'a self,        path: &'a PathUri,        contents: Vec<u8>,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(LocalFileSystem::write_file(self, path, contents, sandbox))    }    fn create_directory<'a>(        &'a self,        path: &'a PathUri,        options: CreateDirectoryOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(LocalFileSystem::create_directory(            self, path, options, sandbox,        ))    }    fn get_metadata<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileMetadata> {        Box::pin(LocalFileSystem::get_metadata(self, path, sandbox))    }    fn read_directory<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {        Box::pin(LocalFileSystem::read_directory(self, path, sandbox))    }    fn remove<'a>(        &'a self,        path: &'a PathUri,        options: RemoveOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(LocalFileSystem::remove(self, path, options, sandbox))    }    fn copy<'a>(        &'a self,        source_path: &'a PathUri,        destination_path: &'a PathUri,        options: CopyOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(LocalFileSystem::copy(            self,            source_path,            destination_path,            options,            sandbox,        ))    }}impl UnsandboxedFileSystem {    async fn open_file_for_read(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<tokio::fs::File> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .open_file_for_read(path, /*sandbox*/ None)            .await    }    async fn canonicalize(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<PathUri> {        reject_platform_sandbox_context(sandbox)?;        self.file_system.canonicalize(path, /*sandbox*/ None).await    }    async fn read_file(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<Vec<u8>> {        reject_platform_sandbox_context(sandbox)?;        self.file_system.read_file(path, /*sandbox*/ None).await    }    async fn read_file_stream(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<FileSystemReadStream> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .read_file_stream(path, /*sandbox*/ None)            .await    }    async fn write_file(        &self,        path: &PathUri,        contents: Vec<u8>,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .write_file(path, contents, /*sandbox*/ None)            .await    }    async fn create_directory(        &self,        path: &PathUri,        options: CreateDirectoryOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .create_directory(path, options, /*sandbox*/ None)            .await    }    async fn get_metadata(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<FileMetadata> {        reject_platform_sandbox_context(sandbox)?;        self.file_system.get_metadata(path, /*sandbox*/ None).await    }    async fn read_directory(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<Vec<ReadDirectoryEntry>> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .read_directory(path, /*sandbox*/ None)            .await    }    async fn remove(        &self,        path: &PathUri,        options: RemoveOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .remove(path, options, /*sandbox*/ None)            .await    }    async fn copy(        &self,        source_path: &PathUri,        destination_path: &PathUri,        options: CopyOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_platform_sandbox_context(sandbox)?;        self.file_system            .copy(                source_path,                destination_path,                options,                /*sandbox*/ None,            )            .await    }}impl ExecutorFileSystem for UnsandboxedFileSystem {    fn canonicalize<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, PathUri> {        Box::pin(UnsandboxedFileSystem::canonicalize(self, path, sandbox))    }    fn read_file<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<u8>> {        Box::pin(UnsandboxedFileSystem::read_file(self, path, sandbox))    }    fn read_file_stream<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream> {        Box::pin(UnsandboxedFileSystem::read_file_stream(self, path, sandbox))    }    fn write_file<'a>(        &'a self,        path: &'a PathUri,        contents: Vec<u8>,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(UnsandboxedFileSystem::write_file(            self, path, contents, sandbox,        ))    }    fn create_directory<'a>(        &'a self,        path: &'a PathUri,        options: CreateDirectoryOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(UnsandboxedFileSystem::create_directory(            self, path, options, sandbox,        ))    }    fn get_metadata<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileMetadata> {        Box::pin(UnsandboxedFileSystem::get_metadata(self, path, sandbox))    }    fn read_directory<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {        Box::pin(UnsandboxedFileSystem::read_directory(self, path, sandbox))    }    fn remove<'a>(        &'a self,        path: &'a PathUri,        options: RemoveOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(UnsandboxedFileSystem::remove(self, path, options, sandbox))    }    fn copy<'a>(        &'a self,        source_path: &'a PathUri,        destination_path: &'a PathUri,        options: CopyOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(UnsandboxedFileSystem::copy(            self,            source_path,            destination_path,            options,            sandbox,        ))    }}impl DirectFileSystem {    async fn open_file_for_read(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<tokio::fs::File> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        regular_file::open(path.as_path()).await    }    async fn canonicalize(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<PathUri> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        let canonicalized =            AbsolutePathBuf::from_absolute_path(tokio::fs::canonicalize(path.as_path()).await?)?;        Ok(PathUri::from_abs_path(&canonicalized))    }    async fn read_file(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<Vec<u8>> {        let file = self.open_file_for_read(path, sandbox).await?;        let metadata = file.metadata().await?;        if metadata.len() > MAX_READ_FILE_BYTES {            return Err(file_too_large_error());        }        let mut bytes = Vec::with_capacity(metadata.len() as usize);        file.take(MAX_READ_FILE_BYTES + 1)            .read_to_end(&mut bytes)            .await?;        if bytes.len() as u64 > MAX_READ_FILE_BYTES {            return Err(file_too_large_error());        }        Ok(bytes)    }    async fn read_file_stream(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<FileSystemReadStream> {        let file = self.open_file_for_read(path, sandbox).await?;        Ok(FileSystemReadStream::new(ReaderStream::with_capacity(            file,            FILE_READ_CHUNK_SIZE,        )))    }    async fn write_file(        &self,        path: &PathUri,        contents: Vec<u8>,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        tokio::fs::write(path.as_path(), contents).await    }    async fn create_directory(        &self,        path: &PathUri,        options: CreateDirectoryOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        if options.recursive {            tokio::fs::create_dir_all(path.as_path()).await?;        } else {            tokio::fs::create_dir(path.as_path()).await?;        }        Ok(())    }    async fn get_metadata(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<FileMetadata> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        let metadata = tokio::fs::metadata(path.as_path()).await?;        let symlink_metadata = tokio::fs::symlink_metadata(path.as_path()).await?;        Ok(FileMetadata {            is_directory: metadata.is_dir(),            is_file: metadata.is_file(),            is_symlink: symlink_metadata.file_type().is_symlink(),            size: metadata.len(),            created_at_ms: metadata.created().ok().map_or(0, system_time_to_unix_ms),            modified_at_ms: metadata.modified().ok().map_or(0, system_time_to_unix_ms),        })    }    async fn read_directory(        &self,        path: &PathUri,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<Vec<ReadDirectoryEntry>> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        let mut entries = Vec::new();        let mut read_dir = tokio::fs::read_dir(path.as_path()).await?;        while let Some(entry) = read_dir.next_entry().await? {            let Ok(metadata) = tokio::fs::metadata(entry.path()).await else {                continue;            };            entries.push(ReadDirectoryEntry {                file_name: entry.file_name().to_string_lossy().into_owned(),                is_directory: metadata.is_dir(),                is_file: metadata.is_file(),            });        }        Ok(entries)    }    async fn remove(        &self,        path: &PathUri,        options: RemoveOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_sandbox_context(sandbox)?;        let path = path.to_abs_path()?;        match tokio::fs::symlink_metadata(path.as_path()).await {            Ok(metadata) => {                let file_type = metadata.file_type();                if file_type.is_dir() {                    if options.recursive {                        tokio::fs::remove_dir_all(path.as_path()).await?;                    } else {                        tokio::fs::remove_dir(path.as_path()).await?;                    }                } else {                    tokio::fs::remove_file(path.as_path()).await?;                }                Ok(())            }            Err(err) if err.kind() == io::ErrorKind::NotFound && options.force => Ok(()),            Err(err) => Err(err),        }    }    async fn copy(        &self,        source_path: &PathUri,        destination_path: &PathUri,        options: CopyOptions,        sandbox: Option<&FileSystemSandboxContext>,    ) -> FileSystemResult<()> {        reject_sandbox_context(sandbox)?;        let source_path = source_path.to_abs_path()?.into_path_buf();        let destination_path = destination_path.to_abs_path()?.into_path_buf();        tokio::task::spawn_blocking(move || -> FileSystemResult<()> {            let metadata = std::fs::symlink_metadata(source_path.as_path())?;            let file_type = metadata.file_type();            if file_type.is_dir() {                if !options.recursive {                    return Err(io::Error::new(                        io::ErrorKind::InvalidInput,                        "fs/copy requires recursive: true when sourcePath is a directory",                    ));                }                if destination_is_same_or_descendant_of_source(                    source_path.as_path(),                    destination_path.as_path(),                )? {                    return Err(io::Error::new(                        io::ErrorKind::InvalidInput,                        "fs/copy cannot copy a directory to itself or one of its descendants",                    ));                }                copy_dir_recursive(source_path.as_path(), destination_path.as_path())?;                return Ok(());            }            if file_type.is_symlink() {                copy_symlink(source_path.as_path(), destination_path.as_path())?;                return Ok(());            }            if file_type.is_file() {                std::fs::copy(source_path.as_path(), destination_path.as_path())?;                return Ok(());            }            Err(io::Error::new(                io::ErrorKind::InvalidInput,                "fs/copy only supports regular files, directories, and symlinks",            ))        })        .await        .map_err(|err| io::Error::other(format!("filesystem task failed: {err}")))?    }}impl ExecutorFileSystem for DirectFileSystem {    fn canonicalize<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, PathUri> {        Box::pin(DirectFileSystem::canonicalize(self, path, sandbox))    }    fn read_file<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<u8>> {        Box::pin(DirectFileSystem::read_file(self, path, sandbox))    }    fn read_file_stream<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream> {        Box::pin(DirectFileSystem::read_file_stream(self, path, sandbox))    }    fn write_file<'a>(        &'a self,        path: &'a PathUri,        contents: Vec<u8>,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(DirectFileSystem::write_file(self, path, contents, sandbox))    }    fn create_directory<'a>(        &'a self,        path: &'a PathUri,        options: CreateDirectoryOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(DirectFileSystem::create_directory(            self, path, options, sandbox,        ))    }    fn get_metadata<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileMetadata> {        Box::pin(DirectFileSystem::get_metadata(self, path, sandbox))    }    fn read_directory<'a>(        &'a self,        path: &'a PathUri,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {        Box::pin(DirectFileSystem::read_directory(self, path, sandbox))    }    fn remove<'a>(        &'a self,        path: &'a PathUri,        options: RemoveOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(DirectFileSystem::remove(self, path, options, sandbox))    }    fn copy<'a>(        &'a self,        source_path: &'a PathUri,        destination_path: &'a PathUri,        options: CopyOptions,        sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(DirectFileSystem::copy(            self,            source_path,            destination_path,            options,            sandbox,        ))    }}fn reject_sandbox_context(sandbox: Option<&FileSystemSandboxContext>) -> io::Result<()> {    if sandbox.is_some() {        return Err(io::Error::new(            io::ErrorKind::InvalidInput,            "direct filesystem operations do not accept sandbox context",        ));    }    Ok(())}fn reject_platform_sandbox_context(sandbox: Option<&FileSystemSandboxContext>) -> io::Result<()> {    if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox) {        return Err(io::Error::new(            io::ErrorKind::InvalidInput,            "sandboxed filesystem operations require configured runtime paths",        ));    }    Ok(())}fn copy_dir_recursive(source: &Path, target: &Path) -> io::Result<()> {    std::fs::create_dir_all(target)?;    for entry in std::fs::read_dir(source)? {        let entry = entry?;        let source_path = entry.path();        let target_path = target.join(entry.file_name());        let file_type = entry.file_type()?;        if file_type.is_dir() {            copy_dir_recursive(&source_path, &target_path)?;        } else if file_type.is_file() {            std::fs::copy(&source_path, &target_path)?;        } else if file_type.is_symlink() {            copy_symlink(&source_path, &target_path)?;        }    }    Ok(())}fn destination_is_same_or_descendant_of_source(    source: &Path,    destination: &Path,) -> io::Result<bool> {    let source = std::fs::canonicalize(source)?;    let destination = resolve_existing_path(destination)?;    Ok(destination.starts_with(&source))}pub(crate) fn resolve_existing_path(path: &Path) -> io::Result<PathBuf> {    let mut unresolved_suffix = Vec::new();    let mut existing_path = path;    while !existing_path.exists() {        let Some(file_name) = existing_path.file_name() else {            break;        };        unresolved_suffix.push(file_name.to_os_string());        let Some(parent) = existing_path.parent() else {            break;        };        existing_path = parent;    }    let mut resolved = std::fs::canonicalize(existing_path)?;    for file_name in unresolved_suffix.iter().rev() {        resolved.push(file_name);    }    Ok(resolved)}pub(crate) fn current_sandbox_cwd() -> io::Result<PathBuf> {    let cwd = std::env::current_dir()        .map_err(|err| io::Error::other(format!("failed to read current dir: {err}")))?;    resolve_existing_path(cwd.as_path())}fn copy_symlink(source: &Path, target: &Path) -> io::Result<()> {    let link_target = std::fs::read_link(source)?;    #[cfg(unix)]    {        std::os::unix::fs::symlink(&link_target, target)    }    #[cfg(windows)]    {        if symlink_points_to_directory(source)? {            std::os::windows::fs::symlink_dir(&link_target, target)        } else {            std::os::windows::fs::symlink_file(&link_target, target)        }    }    #[cfg(not(any(unix, windows)))]    {        let _ = link_target;        let _ = target;        Err(io::Error::new(            io::ErrorKind::Unsupported,            "copying symlinks is unsupported on this platform",        ))    }}#[cfg(windows)]fn symlink_points_to_directory(source: &Path) -> io::Result<bool> {    use std::os::windows::fs::FileTypeExt;    Ok(std::fs::symlink_metadata(source)?        .file_type()        .is_symlink_dir())}fn system_time_to_unix_ms(time: SystemTime) -> i64 {    time.duration_since(UNIX_EPOCH)        .ok()        .and_then(|duration| i64::try_from(duration.as_millis()).ok())        .unwrap_or(0)}#[cfg(all(test, any(unix, windows)))]#[path = "local_file_system_path_uri_tests.rs"]mod path_uri_tests;#[cfg(all(test, unix))]mod tests {    use super::*;    use pretty_assertions::assert_eq;    use std::os::unix::fs::symlink;    #[test]    fn resolve_existing_path_handles_symlink_parent_dotdot_escape() -> io::Result<()> {        let temp_dir = tempfile::TempDir::new()?;        let allowed_dir = temp_dir.path().join("allowed");        let outside_dir = temp_dir.path().join("outside");        std::fs::create_dir_all(&allowed_dir)?;        std::fs::create_dir_all(&outside_dir)?;        symlink(&outside_dir, allowed_dir.join("link"))?;        let resolved = resolve_existing_path(            allowed_dir                .join("link")                .join("..")                .join("secret.txt")                .as_path(),        )?;        assert_eq!(            resolved,            resolve_existing_path(temp_dir.path())?.join("secret.txt")        );        Ok(())    }}#[cfg(all(test, windows))]mod tests {    use super::*;    use pretty_assertions::assert_eq;    #[test]    fn symlink_points_to_directory_handles_dangling_directory_symlinks() -> io::Result<()> {        use std::os::windows::fs::symlink_dir;        let temp_dir = tempfile::TempDir::new()?;        let source_dir = temp_dir.path().join("source");        let link_path = temp_dir.path().join("source-link");        std::fs::create_dir(&source_dir)?;        if symlink_dir(&source_dir, &link_path).is_err() {            return Ok(());        }        std::fs::remove_dir(&source_dir)?;        assert_eq!(symlink_points_to_directory(&link_path)?, true);        Ok(())    }}