Download codex-rs/exec-server/src/local_file_system.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 46.5 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/src/local_file_system.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/exec-server/src/local_file_system.rs
-
curl -L -o local_file_system.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/src/local_file_system.rs
46.5 kB
| use codex_file_system::MAX_WALK_DEPTH; | |
| use codex_file_system::MAX_WALK_DIRECTORIES; | |
| use codex_file_system::MAX_WALK_ENTRIES; | |
| use codex_file_system::MAX_WALK_RESPONSE_BYTES; | |
| use codex_file_system::WALK_RESPONSE_ITEM_OVERHEAD_BYTES; | |
| use codex_utils_absolute_path::AbsolutePathBuf; | |
| use codex_utils_path_uri::PathUri; | |
| use std::collections::HashSet; | |
| use std::collections::VecDeque; | |
| 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 tokio_util::sync::CancellationToken; | |
| 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::GetMetadataOptions; | |
| use crate::ReadDirectoryEntry; | |
| use crate::ReadFileOptions; | |
| use crate::RemoveOptions; | |
| use crate::WalkEntry; | |
| use crate::WalkEntryKind; | |
| use crate::WalkError; | |
| use crate::WalkOptions; | |
| use crate::WalkOutcome; | |
| use crate::WriteFileOptions; | |
| use crate::no_follow; | |
| 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()) }); | |
| pub(crate) struct DirectFileSystem; | |
| pub(crate) struct UnsandboxedFileSystem { | |
| file_system: DirectFileSystem, | |
| } | |
| 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)), | |
| } | |
| } | |
| pub(crate) 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 self.sandboxed()?.open_file_for_read(path, sandbox).await; | |
| } | |
| 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, | |
| options: ReadFileOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<Vec<u8>> { | |
| let (file_system, sandbox) = self.file_system_for(sandbox)?; | |
| file_system.read_file(path, options, 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>, | |
| options: WriteFileOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<()> { | |
| let (file_system, sandbox) = self.file_system_for(sandbox)?; | |
| file_system | |
| .write_file(path, contents, options, 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, | |
| options: GetMetadataOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<FileMetadata> { | |
| let (file_system, sandbox) = self.file_system_for(sandbox)?; | |
| file_system.get_metadata(path, options, 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 walk( | |
| &self, | |
| path: &PathUri, | |
| options: WalkOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<WalkOutcome> { | |
| let (file_system, sandbox) = self.file_system_for(sandbox)?; | |
| file_system.walk(path, options, 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, | |
| options: ReadFileOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, Vec<u8>> { | |
| Box::pin(LocalFileSystem::read_file(self, path, options, 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>, | |
| options: WriteFileOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, ()> { | |
| Box::pin(LocalFileSystem::write_file( | |
| self, path, contents, options, 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, | |
| options: GetMetadataOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, FileMetadata> { | |
| Box::pin(LocalFileSystem::get_metadata(self, path, options, 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 walk<'a>( | |
| &'a self, | |
| path: &'a PathUri, | |
| options: WalkOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, WalkOutcome> { | |
| Box::pin(LocalFileSystem::walk(self, path, options, 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, | |
| options: ReadFileOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<Vec<u8>> { | |
| reject_platform_sandbox_context(sandbox)?; | |
| self.file_system | |
| .read_file(path, options, /*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>, | |
| options: WriteFileOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<()> { | |
| reject_platform_sandbox_context(sandbox)?; | |
| self.file_system | |
| .write_file(path, contents, options, /*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, | |
| options: GetMetadataOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<FileMetadata> { | |
| reject_platform_sandbox_context(sandbox)?; | |
| self.file_system | |
| .get_metadata(path, options, /*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, | |
| options: ReadFileOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, Vec<u8>> { | |
| Box::pin(UnsandboxedFileSystem::read_file( | |
| self, path, options, 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>, | |
| options: WriteFileOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, ()> { | |
| Box::pin(UnsandboxedFileSystem::write_file( | |
| self, path, contents, options, 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, | |
| options: GetMetadataOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, FileMetadata> { | |
| Box::pin(UnsandboxedFileSystem::get_metadata( | |
| self, path, options, 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 walk<'a>( | |
| &'a self, | |
| path: &'a PathUri, | |
| options: WalkOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, WalkOutcome> { | |
| Box::pin(async move { | |
| reject_platform_sandbox_context(sandbox)?; | |
| self.file_system.walk(path, options, /*sandbox*/ None).await | |
| }) | |
| } | |
| 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, | |
| options: ReadFileOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<Vec<u8>> { | |
| reject_sandbox_context(sandbox)?; | |
| let file = if options.follow_symlinks { | |
| self.open_file_for_read(path, /*sandbox*/ None).await? | |
| } else { | |
| no_follow::open_file(path.to_abs_path()?.as_path()).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>, | |
| options: WriteFileOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<()> { | |
| reject_sandbox_context(sandbox)?; | |
| let path = path.to_abs_path()?; | |
| if options.follow_symlinks { | |
| tokio::fs::write(path.as_path(), contents).await | |
| } else { | |
| no_follow::write_file(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.follow_symlinks { | |
| return no_follow::create_directory(path.as_path(), options.recursive).await; | |
| } | |
| 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, | |
| options: GetMetadataOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<FileMetadata> { | |
| reject_sandbox_context(sandbox)?; | |
| let path = path.to_abs_path()?; | |
| if !options.follow_symlinks { | |
| return no_follow::metadata(path.as_path()).await; | |
| } | |
| let symlink_metadata = tokio::fs::symlink_metadata(path.as_path()).await?; | |
| let is_symlink = symlink_metadata.is_symlink(); | |
| let metadata = if is_symlink { | |
| tokio::fs::metadata(path.as_path()).await? | |
| } else { | |
| symlink_metadata | |
| }; | |
| Ok(file_metadata(metadata, is_symlink)) | |
| } | |
| 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(mut file_type) = entry.file_type().await else { | |
| continue; | |
| }; | |
| if file_type.is_symlink() { | |
| let Ok(metadata) = tokio::fs::metadata(entry.path()).await else { | |
| continue; | |
| }; | |
| file_type = metadata.file_type(); | |
| } | |
| entries.push(ReadDirectoryEntry { | |
| file_name: entry.file_name().to_string_lossy().into_owned(), | |
| is_directory: file_type.is_dir(), | |
| is_file: file_type.is_file(), | |
| }); | |
| } | |
| Ok(entries) | |
| } | |
| fn sync_walk( | |
| root: &PathUri, | |
| options: WalkOptions, | |
| cancelled: &CancellationToken, | |
| ) -> io::Result<WalkOutcome> { | |
| if options.max_directories == 0 || options.max_entries == 0 { | |
| return Err(io::Error::new( | |
| io::ErrorKind::InvalidInput, | |
| "filesystem walk limits must be greater than zero", | |
| )); | |
| } | |
| if options.max_depth > MAX_WALK_DEPTH | |
| || options.max_directories > MAX_WALK_DIRECTORIES | |
| || options.max_entries > MAX_WALK_ENTRIES | |
| { | |
| return Err(io::Error::new( | |
| io::ErrorKind::InvalidInput, | |
| format!( | |
| "filesystem walk limits exceed maximums: depth={MAX_WALK_DEPTH}, directories={MAX_WALK_DIRECTORIES}, entries={MAX_WALK_ENTRIES}" | |
| ), | |
| )); | |
| } | |
| check_walk_cancelled(cancelled)?; | |
| let (root_metadata, root_is_symlink) = walk_metadata(root)?; | |
| if !root_metadata.is_dir() || (root_is_symlink && !options.follow_directory_symlinks) { | |
| return Ok(WalkOutcome::default()); | |
| } | |
| let root_identity = if options.follow_directory_symlinks { | |
| check_walk_cancelled(cancelled)?; | |
| walk_canonicalize(root)? | |
| } else { | |
| root.clone() | |
| }; | |
| let mut outcome = WalkOutcome::default(); | |
| let mut queue = VecDeque::from([(root.clone(), 0usize)]); | |
| let mut visited_directories = HashSet::from([root_identity]); | |
| let mut directory_count = 1usize; | |
| let mut entry_count = 0usize; | |
| let mut response_bytes = 0usize; | |
| while let Some((directory, depth)) = queue.pop_front() { | |
| let entries = walk_read_directory(&directory, cancelled); | |
| check_walk_cancelled(cancelled)?; | |
| let mut entries = match entries { | |
| Ok(entries) => entries, | |
| Err(error) => { | |
| if !push_walk_error( | |
| &mut outcome, | |
| &mut response_bytes, | |
| directory, | |
| error.to_string(), | |
| ) { | |
| return Ok(outcome); | |
| } | |
| continue; | |
| } | |
| }; | |
| entries.sort(); | |
| for file_name in entries { | |
| check_walk_cancelled(cancelled)?; | |
| if entry_count == options.max_entries { | |
| outcome.truncated = true; | |
| return Ok(outcome); | |
| } | |
| entry_count += 1; | |
| let path = match directory.join(&file_name) { | |
| Ok(path) => path, | |
| Err(error) => { | |
| if !push_walk_error( | |
| &mut outcome, | |
| &mut response_bytes, | |
| directory.clone(), | |
| error.to_string(), | |
| ) { | |
| return Ok(outcome); | |
| } | |
| continue; | |
| } | |
| }; | |
| let (metadata, is_symlink) = match walk_metadata(&path) { | |
| Ok(metadata) => metadata, | |
| Err(error) => { | |
| if !push_walk_error( | |
| &mut outcome, | |
| &mut response_bytes, | |
| path, | |
| error.to_string(), | |
| ) { | |
| return Ok(outcome); | |
| } | |
| continue; | |
| } | |
| }; | |
| if is_symlink && (!options.follow_directory_symlinks || !metadata.is_dir()) { | |
| continue; | |
| } | |
| let kind = if metadata.is_dir() { | |
| WalkEntryKind::Directory | |
| } else if metadata.is_file() { | |
| WalkEntryKind::File | |
| } else { | |
| continue; | |
| }; | |
| if !reserve_walk_response_bytes( | |
| &mut outcome, | |
| &mut response_bytes, | |
| path.to_string().len(), | |
| ) { | |
| return Ok(outcome); | |
| } | |
| outcome.entries.push(WalkEntry { | |
| path: path.clone(), | |
| kind, | |
| }); | |
| if kind == WalkEntryKind::Directory && depth < options.max_depth { | |
| if options.prune_hidden_directories && file_name.starts_with('.') { | |
| continue; | |
| } | |
| let directory_identity = if options.follow_directory_symlinks { | |
| check_walk_cancelled(cancelled)?; | |
| match walk_canonicalize(&path) { | |
| Ok(path) => path, | |
| Err(error) => { | |
| if !push_walk_error( | |
| &mut outcome, | |
| &mut response_bytes, | |
| path, | |
| error.to_string(), | |
| ) { | |
| return Ok(outcome); | |
| } | |
| continue; | |
| } | |
| } | |
| } else { | |
| path.clone() | |
| }; | |
| if !visited_directories.insert(directory_identity) { | |
| continue; | |
| } | |
| if directory_count == options.max_directories { | |
| outcome.truncated = true; | |
| } else { | |
| directory_count += 1; | |
| queue.push_back((path, depth + 1)); | |
| } | |
| } | |
| } | |
| } | |
| Ok(outcome) | |
| } | |
| async fn remove( | |
| &self, | |
| path: &PathUri, | |
| options: RemoveOptions, | |
| sandbox: Option<&FileSystemSandboxContext>, | |
| ) -> FileSystemResult<()> { | |
| reject_sandbox_context(sandbox)?; | |
| let path = path.to_abs_path()?; | |
| if !options.follow_symlinks { | |
| return no_follow::remove(path.as_path(), options.recursive, options.force).await; | |
| } | |
| 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, | |
| options: ReadFileOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, Vec<u8>> { | |
| Box::pin(DirectFileSystem::read_file(self, path, options, 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>, | |
| options: WriteFileOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, ()> { | |
| Box::pin(DirectFileSystem::write_file( | |
| self, path, contents, options, 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, | |
| options: GetMetadataOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, FileMetadata> { | |
| Box::pin(DirectFileSystem::get_metadata(self, path, options, 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 walk<'a>( | |
| &'a self, | |
| path: &'a PathUri, | |
| options: WalkOptions, | |
| sandbox: Option<&'a FileSystemSandboxContext>, | |
| ) -> ExecutorFileSystemFuture<'a, WalkOutcome> { | |
| Box::pin(async move { | |
| reject_sandbox_context(sandbox)?; | |
| let path = path.clone(); | |
| let cancelled = CancellationToken::new(); | |
| let _cancel_on_drop = cancelled.clone().drop_guard(); | |
| tokio::task::spawn_blocking(move || Self::sync_walk(&path, options, &cancelled)) | |
| .await | |
| .map_err(|err| io::Error::other(format!("filesystem task failed: {err}")))? | |
| }) | |
| } | |
| 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 check_walk_cancelled(cancelled: &CancellationToken) -> io::Result<()> { | |
| if cancelled.is_cancelled() { | |
| return Err(io::Error::new( | |
| io::ErrorKind::Interrupted, | |
| "filesystem walk cancelled", | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| fn walk_metadata(path: &PathUri) -> io::Result<(std::fs::Metadata, bool)> { | |
| let path = path.to_abs_path()?; | |
| let metadata = std::fs::symlink_metadata(path.as_path())?; | |
| let is_symlink = metadata.is_symlink(); | |
| let metadata = if is_symlink { | |
| std::fs::metadata(path.as_path())? | |
| } else { | |
| metadata | |
| }; | |
| Ok((metadata, is_symlink)) | |
| } | |
| fn walk_canonicalize(path: &PathUri) -> io::Result<PathUri> { | |
| let path = path.to_abs_path()?; | |
| let canonicalized = | |
| AbsolutePathBuf::from_absolute_path(std::fs::canonicalize(path.as_path())?)?; | |
| Ok(PathUri::from_abs_path(&canonicalized)) | |
| } | |
| fn walk_read_directory(path: &PathUri, cancelled: &CancellationToken) -> io::Result<Vec<String>> { | |
| check_walk_cancelled(cancelled)?; | |
| let path = path.to_abs_path()?; | |
| let mut entries = Vec::new(); | |
| for entry in std::fs::read_dir(path.as_path())? { | |
| check_walk_cancelled(cancelled)?; | |
| let entry = entry?; | |
| let Ok(file_type) = entry.file_type() else { | |
| continue; | |
| }; | |
| // Match DirectFileSystem::read_directory: omit broken or inaccessible links. | |
| if file_type.is_symlink() { | |
| check_walk_cancelled(cancelled)?; | |
| if std::fs::metadata(entry.path()).is_err() { | |
| continue; | |
| } | |
| } | |
| entries.push(entry.file_name().to_string_lossy().into_owned()); | |
| } | |
| Ok(entries) | |
| } | |
| fn push_walk_error( | |
| outcome: &mut WalkOutcome, | |
| response_bytes: &mut usize, | |
| path: PathUri, | |
| message: String, | |
| ) -> bool { | |
| let item_bytes = path.to_string().len().saturating_add(message.len()); | |
| if !reserve_walk_response_bytes(outcome, response_bytes, item_bytes) { | |
| return false; | |
| } | |
| outcome.errors.push(WalkError { path, message }); | |
| true | |
| } | |
| fn reserve_walk_response_bytes( | |
| outcome: &mut WalkOutcome, | |
| response_bytes: &mut usize, | |
| content_bytes: usize, | |
| ) -> bool { | |
| let item_bytes = content_bytes.saturating_add(WALK_RESPONSE_ITEM_OVERHEAD_BYTES); | |
| let Some(total_bytes) = response_bytes.checked_add(item_bytes) else { | |
| outcome.truncated = true; | |
| return false; | |
| }; | |
| if total_bytes > MAX_WALK_RESPONSE_BYTES { | |
| outcome.truncated = true; | |
| return false; | |
| } | |
| *response_bytes = total_bytes; | |
| true | |
| } | |
| 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 file_metadata(metadata: std::fs::Metadata, is_symlink: bool) -> FileMetadata { | |
| FileMetadata { | |
| is_directory: metadata.is_dir(), | |
| is_file: metadata.is_file(), | |
| 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), | |
| } | |
| } | |
| 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)?; | |
| { | |
| std::os::unix::fs::symlink(&link_target, target) | |
| } | |
| { | |
| 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) | |
| } | |
| } | |
| { | |
| let _ = link_target; | |
| let _ = target; | |
| Err(io::Error::new( | |
| io::ErrorKind::Unsupported, | |
| "copying symlinks is unsupported on this platform", | |
| )) | |
| } | |
| } | |
| 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) | |
| } | |
| mod path_uri_tests; | |
| mod tests { | |
| use super::*; | |
| use pretty_assertions::assert_eq; | |
| use std::os::unix::fs::symlink; | |
| 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(()) | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| use pretty_assertions::assert_eq; | |
| 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(()) | |
| } | |
| } | |
| mod walk_tests { | |
| use super::*; | |
| use codex_protocol::models::PermissionProfile; | |
| use codex_protocol::permissions::FileSystemSandboxPolicy; | |
| use codex_protocol::permissions::NetworkSandboxPolicy; | |
| use pretty_assertions::assert_eq; | |
| async fn sync_walk_rejects_sandbox_context() -> io::Result<()> { | |
| let temp = tempfile::tempdir()?; | |
| let root = PathUri::from_host_native_path(temp.path())?; | |
| let sandbox = FileSystemSandboxContext::from_permission_profile( | |
| PermissionProfile::from_runtime_permissions( | |
| &FileSystemSandboxPolicy::restricted(Vec::new()), | |
| NetworkSandboxPolicy::Restricted, | |
| ), | |
| ); | |
| let options = WalkOptions { | |
| max_depth: 1, | |
| max_directories: 1, | |
| max_entries: 1, | |
| follow_directory_symlinks: false, | |
| prune_hidden_directories: false, | |
| }; | |
| let direct_error = DirectFileSystem | |
| .walk(&root, options, Some(&sandbox)) | |
| .await | |
| .expect_err("direct walk must reject sandbox contexts"); | |
| let wrapper_error = UnsandboxedFileSystem::default() | |
| .walk(&root, options, Some(&sandbox)) | |
| .await | |
| .expect_err("unsandboxed walk must reject restricted contexts"); | |
| assert_eq!(direct_error.kind(), io::ErrorKind::InvalidInput); | |
| assert_eq!(wrapper_error.kind(), io::ErrorKind::InvalidInput); | |
| Ok(()) | |
| } | |
| fn sync_walk_cancellation_stops_before_io() -> io::Result<()> { | |
| let temp = tempfile::tempdir()?; | |
| let missing = PathUri::from_host_native_path(temp.path().join("missing"))?; | |
| let options = WalkOptions { | |
| max_depth: 1, | |
| max_directories: 1, | |
| max_entries: 1, | |
| follow_directory_symlinks: true, | |
| prune_hidden_directories: false, | |
| }; | |
| let cancelled = CancellationToken::new(); | |
| let cancel_on_drop = cancelled.clone().drop_guard(); | |
| drop(cancel_on_drop); | |
| for result in [ | |
| DirectFileSystem::sync_walk(&missing, options, &cancelled).map(|_| ()), | |
| walk_read_directory(&missing, &cancelled).map(|_| ()), | |
| ] { | |
| assert_eq!( | |
| result | |
| .expect_err("cancelled walks must stop before I/O") | |
| .kind(), | |
| io::ErrorKind::Interrupted, | |
| ); | |
| } | |
| Ok(()) | |
| } | |
| fn sync_walk_response_budget_counts_entries_and_errors() -> io::Result<()> { | |
| let temp = tempfile::tempdir()?; | |
| let root = PathUri::from_host_native_path(temp.path())?; | |
| let mut outcome = WalkOutcome::default(); | |
| let mut response_bytes = | |
| MAX_WALK_RESPONSE_BYTES - WALK_RESPONSE_ITEM_OVERHEAD_BYTES - root.to_string().len(); | |
| assert!(push_walk_error( | |
| &mut outcome, | |
| &mut response_bytes, | |
| root.clone(), | |
| String::new() | |
| )); | |
| assert!(!reserve_walk_response_bytes( | |
| &mut outcome, | |
| &mut response_bytes, | |
| /*content_bytes*/ 0 | |
| )); | |
| assert_eq!( | |
| outcome, | |
| WalkOutcome { | |
| entries: Vec::new(), | |
| errors: vec![WalkError { | |
| path: root, | |
| message: String::new() | |
| }], | |
| truncated: true, | |
| }, | |
| ); | |
| Ok(()) | |
| } | |
| } | |