use bytes::Bytes;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::models::ManagedFileSystemPermissions;use codex_protocol::models::PermissionProfile;use codex_protocol::models::SandboxEnforcement;use codex_protocol::permissions::FileSystemPath;use codex_protocol::permissions::FileSystemSandboxKind;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::FileSystemSpecialPath;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::SandboxPolicy;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_path_uri::PathUri;use futures::Stream;use std::future::Future;use std::io;use std::path::Path;use std::pin::Pin;use std::task::Context;use std::task::Poll;/// Maximum chunk size returned by [`ExecutorFileSystem::read_file_stream`].pub const FILE_READ_CHUNK_SIZE: usize = 1024 * 1024;#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct CreateDirectoryOptions { pub recursive: bool,}#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct RemoveOptions { pub recursive: bool, pub force: bool,}#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct CopyOptions { pub recursive: bool,}#[derive(Clone, Debug, Eq, PartialEq)]pub struct FileMetadata { pub is_directory: bool, pub is_file: bool, pub is_symlink: bool, /// Size in bytes. pub size: u64, pub created_at_ms: i64, pub modified_at_ms: i64,}#[derive(Clone, Debug, Eq, PartialEq)]pub struct ReadDirectoryEntry { pub file_name: String, pub is_directory: bool, pub is_file: bool,}#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]#[serde(rename_all = "camelCase")]pub struct FileSystemSandboxContext { pub permissions: PermissionProfile<PathUri>, #[serde(default, skip_serializing_if = "Option::is_none")] pub cwd: Option<PathUri>, pub windows_sandbox_level: WindowsSandboxLevel, #[serde(default)] pub windows_sandbox_private_desktop: bool, #[serde(default)] pub use_legacy_landlock: bool,}impl FileSystemSandboxContext { pub fn from_legacy_sandbox_policy( sandbox_policy: SandboxPolicy, cwd: PathUri, ) -> io::Result<Self> { // Legacy policy projection materializes native roots, so convert at the receiving-host // boundary while retaining the URI in the resulting sandbox context. let native_cwd = cwd.to_abs_path()?; let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd( &sandbox_policy, &native_cwd, ); let permissions = PermissionProfile::<AbsolutePathBuf>::from_runtime_permissions_with_enforcement( SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), &file_system_sandbox_policy, NetworkSandboxPolicy::from(&sandbox_policy), ); Ok(Self::from_permission_profile_with_cwd(permissions, cwd)) } pub fn from_permission_profile(permissions: PermissionProfile<AbsolutePathBuf>) -> Self { Self::from_permissions_and_cwd(permissions, /*cwd*/ None) } pub fn from_permission_profile_with_cwd( permissions: PermissionProfile<AbsolutePathBuf>, cwd: PathUri, ) -> Self { Self::from_permissions_and_cwd(permissions, Some(cwd)) } fn from_permissions_and_cwd( permissions: PermissionProfile<AbsolutePathBuf>, cwd: Option<PathUri>, ) -> Self { Self { permissions: permissions.into(), cwd, windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, use_legacy_landlock: false, } } pub fn should_run_in_sandbox(&self) -> bool { let Ok(permissions) = PermissionProfile::<AbsolutePathBuf>::try_from(self.permissions.clone()) else { // A sandbox context for another host must not select the unsandboxed filesystem. return true; }; let file_system_policy = permissions.file_system_sandbox_policy(); matches!(file_system_policy.kind, FileSystemSandboxKind::Restricted) && !file_system_policy.has_full_disk_write_access() } pub fn has_cwd_dependent_permissions(&self) -> bool { match &self.permissions { PermissionProfile::Managed { file_system: ManagedFileSystemPermissions::Restricted { entries, .. }, .. } => entries.iter().any(|entry| match &entry.path { FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(), FileSystemPath::Special { value: FileSystemSpecialPath::ProjectRoots { .. }, } => true, FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false, }), PermissionProfile::Managed { file_system: ManagedFileSystemPermissions::Unrestricted, .. } | PermissionProfile::Disabled | PermissionProfile::External { .. } => false, } } pub fn drop_cwd_if_unused(mut self) -> Self { if !self.has_cwd_dependent_permissions() { self.cwd = None; } self }}pub type FileSystemResult<T> = io::Result<T>;/// Future returned by [`ExecutorFileSystem`] operations.pub type ExecutorFileSystemFuture<'a, T> = Pin<Box<dyn Future<Output = FileSystemResult<T>> + Send + 'a>>;/// Stream of immutable chunks read from an [`ExecutorFileSystem`].pub struct FileSystemReadStream { inner: Pin<Box<dyn Stream<Item = FileSystemResult<Bytes>> + Send + 'static>>,}impl FileSystemReadStream { /// Wraps a filesystem byte stream. pub fn new(stream: impl Stream<Item = FileSystemResult<Bytes>> + Send + 'static) -> Self { Self { inner: Box::pin(stream), } }}impl Stream for FileSystemReadStream { type Item = FileSystemResult<Bytes>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { self.inner.as_mut().poll_next(cx) }}/// Abstract filesystem access used by components that may operate locally or via/// a remote environment.pub trait ExecutorFileSystem: Send + Sync { /// Resolves a path within this filesystem. fn canonicalize<'a>( &'a self, path: &'a PathUri, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, PathUri>; fn read_file<'a>( &'a self, path: &'a PathUri, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, Vec<u8>>; /// Reads a file as a stream of chunks no larger than [`FILE_READ_CHUNK_SIZE`]. fn read_file_stream<'a>( &'a self, path: &'a PathUri, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream>; /// Reads a file and decodes it as UTF-8 text. fn read_file_text<'a>( &'a self, path: &'a PathUri, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, String> { Box::pin(async move { let bytes = self.read_file(path, sandbox).await?; String::from_utf8(bytes).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) }) } fn write_file<'a>( &'a self, path: &'a PathUri, contents: Vec<u8>, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, ()>; fn create_directory<'a>( &'a self, path: &'a PathUri, create_directory_options: CreateDirectoryOptions, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, ()>; fn get_metadata<'a>( &'a self, path: &'a PathUri, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, FileMetadata>; fn read_directory<'a>( &'a self, path: &'a PathUri, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>>; fn remove<'a>( &'a self, path: &'a PathUri, remove_options: RemoveOptions, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, ()>; fn copy<'a>( &'a self, source_path: &'a PathUri, destination_path: &'a PathUri, copy_options: CopyOptions, sandbox: Option<&'a FileSystemSandboxContext>, ) -> ExecutorFileSystemFuture<'a, ()>;}