Files
codex/codex-rs/exec-server/tests/file_system/shared.rs
T
pakrym-oai 76d8f20241 [codex] Add size to internal filesystem metadata (#27927)
## Why

`ExecutorFileSystem::get_metadata` reports file kind and timestamps but
not size. Internal callers that need to enforce a size limit therefore
have to read the complete file first, which is especially wasteful for
remote filesystems.

This adds the missing internal metadata so consumers can reject
oversized files before transferring or buffering them. The field is
named `size`, matching VS Code's `FileStat.size` filesystem convention.

## What changed

- add `size: u64` to internal `FileMetadata`
- populate it from the underlying filesystem metadata
- carry it through sandbox-helper and remote exec-server responses
- cover files, directories, symlink targets, and sandboxed reads across
local and remote filesystem implementations

The new field is intentionally not exposed through the app-server API.

## Testing

- `just test -p codex-exec-server get_metadata`
- `just test -p codex-exec-server
file_system_sandboxed_metadata_and_read_allow_readable_root`
- `just test -p codex-core-plugins`
- `just test -p codex-skills-extension`
2026-06-12 12:12:08 -07:00

597 lines
20 KiB
Rust

use anyhow::Context;
use anyhow::Result;
use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::FileMetadata;
use codex_exec_server::ReadDirectoryEntry;
use codex_exec_server::RemoveOptions;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use std::path::Path;
use tempfile::TempDir;
use test_case::test_case;
use super::support::FileSystemImplementation;
use super::support::absolute_path;
use super::support::create_file_system_context;
use super::support::read_only_sandbox;
use super::support::workspace_write_sandbox;
#[test]
fn sandbox_context_from_profile_preserves_workspace_write_read_only_subpaths() -> Result<()> {
let tmp = TempDir::new()?;
let writable_dir = tmp.path().join("writable");
let git_dir = writable_dir.join(".git");
std::fs::create_dir_all(&git_dir)?;
let sandbox = workspace_write_sandbox(writable_dir.clone());
let policy = sandbox.permissions.file_system_sandbox_policy();
let cwd = absolute_path(writable_dir.clone());
let writable_roots = policy.get_writable_roots_with_cwd(cwd.as_path());
let writable_dir = absolute_path(std::fs::canonicalize(writable_dir)?);
let git_dir = absolute_path(std::fs::canonicalize(git_dir)?);
let Some(writable_root) = writable_roots
.iter()
.find(|writable_root| writable_root.root == writable_dir)
else {
panic!("writable root should be preserved");
};
assert!(writable_root.read_only_subpaths.contains(&git_dir));
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_get_metadata_reports_files_and_directories(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let file_path = tmp.path().join("note.txt");
let directory_path = tmp.path().join("notes");
std::fs::write(&file_path, "hello")?;
std::fs::create_dir(&directory_path)?;
let file_metadata = file_system
.get_metadata(&PathUri::from_path(&file_path)?, /*sandbox*/ None)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
file_metadata,
FileMetadata {
is_directory: false,
is_file: true,
is_symlink: false,
size: 5,
created_at_ms: file_metadata.created_at_ms,
modified_at_ms: file_metadata.modified_at_ms,
}
);
assert!(file_metadata.modified_at_ms > 0);
let directory_metadata = file_system
.get_metadata(&PathUri::from_path(&directory_path)?, /*sandbox*/ None)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
directory_metadata,
FileMetadata {
is_directory: true,
is_file: false,
is_symlink: false,
size: std::fs::metadata(&directory_path)?.len(),
created_at_ms: directory_metadata.created_at_ms,
modified_at_ms: directory_metadata.modified_at_ms,
}
);
assert!(directory_metadata.modified_at_ms > 0);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_create_directory_creates_nested_directories(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let nested_dir = tmp.path().join("source").join("nested");
file_system
.create_directory(
&PathUri::from_path(&nested_dir)?,
CreateDirectoryOptions { recursive: true },
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert!(nested_dir.is_dir());
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_write_file_writes_bytes(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let file_path = tmp.path().join("note.txt");
file_system
.write_file(
&PathUri::from_path(&file_path)?,
b"hello from trait".to_vec(),
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(std::fs::read(file_path)?, b"hello from trait");
Ok(())
}
#[test]
fn path_uri_join_and_parent_preserve_lexical_paths() -> Result<()> {
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
let source_dir_uri = PathUri::from_path(&source_dir)?;
let joined_nested = source_dir_uri.join("nested/note.txt")?;
assert_eq!(
joined_nested,
PathUri::from_path(source_dir.join("nested").join("note.txt"))?
);
let joined_parent = joined_nested.parent();
assert_eq!(
joined_parent,
Some(PathUri::from_path(source_dir.join("nested"))?)
);
let joined_parent_traversal = source_dir_uri.join("../outside")?;
assert_eq!(
joined_parent_traversal,
PathUri::from_path(source_dir.join("../outside"))?
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_read_file_returns_bytes(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let file_path = tmp.path().join("note.txt");
std::fs::write(&file_path, "hello from trait")?;
let contents = file_system
.read_file(&PathUri::from_path(&file_path)?, /*sandbox*/ None)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(contents, b"hello from trait");
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_read_file_text_returns_string(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let file_path = tmp.path().join("note.txt");
std::fs::write(&file_path, "hello from trait")?;
let contents = file_system
.read_file_text(&PathUri::from_path(&file_path)?, /*sandbox*/ None)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(contents, "hello from trait");
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_copy_copies_file(implementation: FileSystemImplementation) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_file = tmp.path().join("source.txt");
let copied_file = tmp.path().join("copy.txt");
std::fs::write(&source_file, "hello from trait")?;
file_system
.copy(
&PathUri::from_path(&source_file)?,
&PathUri::from_path(&copied_file)?,
CopyOptions { recursive: false },
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(std::fs::read_to_string(copied_file)?, "hello from trait");
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_copy_copies_directory_recursively(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
let nested_dir = source_dir.join("nested");
let nested_file = nested_dir.join("note.txt");
let copied_dir = tmp.path().join("copied");
std::fs::create_dir_all(&nested_dir)?;
std::fs::write(&nested_file, "hello from trait")?;
file_system
.copy(
&PathUri::from_path(&source_dir)?,
&PathUri::from_path(&copied_dir)?,
CopyOptions { recursive: true },
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
std::fs::read_to_string(copied_dir.join("nested").join("note.txt"))?,
"hello from trait"
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_read_directory_lists_entries(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
std::fs::create_dir_all(source_dir.join("nested"))?;
std::fs::write(source_dir.join("root.txt"), "hello")?;
let mut entries = file_system
.read_directory(&PathUri::from_path(&source_dir)?, /*sandbox*/ None)
.await
.with_context(|| format!("mode={implementation}"))?;
entries.sort_by(|left, right| left.file_name.cmp(&right.file_name));
assert_eq!(
entries,
vec![
ReadDirectoryEntry {
file_name: "nested".to_string(),
is_directory: true,
is_file: false,
},
ReadDirectoryEntry {
file_name: "root.txt".to_string(),
is_directory: false,
is_file: true,
},
]
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_remove_removes_directory(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let directory_path = tmp.path().join("remove-me");
std::fs::create_dir_all(directory_path.join("nested"))?;
file_system
.remove(
&PathUri::from_path(&directory_path)?,
RemoveOptions {
recursive: true,
force: true,
},
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert!(!directory_path.exists());
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_write_file_reports_missing_parent(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let missing_parent_path = tmp.path().join("missing").join("note.txt");
let error = match file_system
.write_file(
&PathUri::from_path(&missing_parent_path)?,
b"hello from trait".to_vec(),
/*sandbox*/ None,
)
.await
{
Ok(()) => anyhow::bail!("write should fail when parent directory is absent"),
Err(error) => error,
};
assert_eq!(
error.kind(),
std::io::ErrorKind::NotFound,
"mode={implementation}"
);
assert!(!missing_parent_path.exists(), "mode={implementation}");
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_copy_rejects_directory_without_recursive(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
std::fs::create_dir_all(&source_dir)?;
let error = file_system
.copy(
&PathUri::from_path(&source_dir)?,
&PathUri::from_path(tmp.path().join("dest"))?,
CopyOptions { recursive: false },
/*sandbox*/ None,
)
.await;
let error = match error {
Ok(()) => panic!("copy should fail"),
Err(error) => error,
};
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
error.to_string(),
"fs/copy requires recursive: true when sourcePath is a directory"
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_sandboxed_metadata_and_read_allow_readable_root(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let allowed_dir = tmp.path().join("allowed");
let file_path = allowed_dir.join("note.txt");
std::fs::create_dir_all(&allowed_dir)?;
std::fs::write(&file_path, "sandboxed hello")?;
let sandbox = read_only_sandbox(allowed_dir);
let metadata = file_system
.get_metadata(&PathUri::from_path(&file_path)?, Some(&sandbox))
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
metadata,
FileMetadata {
is_directory: false,
is_file: true,
is_symlink: false,
size: 15,
created_at_ms: metadata.created_at_ms,
modified_at_ms: metadata.modified_at_ms,
}
);
let contents = file_system
.read_file(&PathUri::from_path(&file_path)?, Some(&sandbox))
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(contents, b"sandboxed hello");
Ok(())
}
pub(crate) async fn assert_canonicalize_resolves_directory_alias(
implementation: FileSystemImplementation,
create_directory_alias: impl FnOnce(&Path, &Path) -> Result<()>,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
let nested_dir = source_dir.join("nested");
let file_path = nested_dir.join("note.txt");
let alias_dir = tmp.path().join("source-alias");
std::fs::create_dir_all(&nested_dir)?;
std::fs::write(&file_path, "canonical hello")?;
create_directory_alias(&source_dir, &alias_dir)?;
let requested_path = PathUri::from_path(alias_dir.join("nested").join("note.txt"))?;
let expected_path = PathUri::from_path(std::fs::canonicalize(&file_path)?)?;
assert_ne!(requested_path, expected_path);
let canonical_path = file_system
.canonicalize(&requested_path, /*sandbox*/ None)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(canonical_path, expected_path);
Ok(())
}
pub(crate) async fn assert_sandboxed_canonicalize_resolves_directory_alias(
implementation: FileSystemImplementation,
create_directory_alias: impl FnOnce(&Path, &Path) -> Result<()>,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
let nested_dir = source_dir.join("nested");
let file_path = nested_dir.join("note.txt");
let alias_dir = tmp.path().join("source-alias");
std::fs::create_dir_all(&nested_dir)?;
std::fs::write(&file_path, "sandboxed canonical hello")?;
create_directory_alias(&source_dir, &alias_dir)?;
let sandbox = read_only_sandbox(tmp.path().to_path_buf());
let requested_path = PathUri::from_path(alias_dir.join("nested").join("note.txt"))?;
let expected_path = PathUri::from_path(std::fs::canonicalize(&file_path)?)?;
assert_ne!(requested_path, expected_path);
let canonical_path = file_system
.canonicalize(&requested_path, Some(&sandbox))
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(canonical_path, expected_path);
Ok(())
}
/// Verifies that effective additional permissions extend a read-only sandbox with a writable root.
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_sandboxed_write_allows_additional_write_root(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let readable_dir = tmp.path().join("readable");
let writable_dir = tmp.path().join("writable");
let file_path = writable_dir.join("note.txt");
std::fs::create_dir_all(&readable_dir)?;
std::fs::create_dir_all(&writable_dir)?;
let mut sandbox = read_only_sandbox(readable_dir);
let additional_permissions = AdditionalPermissionProfile {
network: None,
file_system: Some(FileSystemPermissions::from_read_write_roots(
/*read*/ None,
Some(vec![absolute_path(writable_dir)]),
)),
};
let file_system_policy = effective_file_system_sandbox_policy(
&sandbox.permissions.file_system_sandbox_policy(),
Some(&additional_permissions),
);
let network_policy = effective_network_sandbox_policy(
sandbox.permissions.network_sandbox_policy(),
Some(&additional_permissions),
);
sandbox.permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
sandbox.permissions.enforcement(),
&file_system_policy,
network_policy,
);
file_system
.write_file(
&PathUri::from_path(&file_path)?,
b"created".to_vec(),
Some(&sandbox),
)
.await
.with_context(|| format!("write file through additional root mode={implementation}"))?;
assert_eq!(std::fs::read(&file_path)?, b"created");
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_copy_rejects_copying_directory_into_descendant(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
std::fs::create_dir_all(source_dir.join("nested"))?;
let error = file_system
.copy(
&PathUri::from_path(&source_dir)?,
&PathUri::from_path(source_dir.join("nested").join("copy"))?,
CopyOptions { recursive: true },
/*sandbox*/ None,
)
.await;
let error = match error {
Ok(()) => panic!("copy should fail"),
Err(error) => error,
};
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
error.to_string(),
"fs/copy cannot copy a directory to itself or one of its descendants"
);
Ok(())
}