Skip to content

ExecServer文件系统RPC

追踪 exec-server 文件系统 RPC 的路径选择、sandbox 分支、流式读取句柄、写入与错误边界。

基于rust-v0.150.0
CodexRustExecutionExecServer

ExecServer文件系统RPC ​

exec-server 的 filesystem RPC 不是把 std::fs 直接暴露给远端。FileSystemHandler 先校验 handle、base64 和目录规模,再把路径与可选 FileSystemSandboxContext 交给 LocalFileSystem;后者依据 sandbox context 选择 unsandboxed 或 platform-sandbox backend。流式读取由独立 FileReadHandleManager 管理,句柄数量、块大小和关闭时机都有明确上限;sandbox backend 同样可以提供受控的文件打开能力。

本文承接ExecServer进程RPC和ExecServer消息模型,面向理解 PathUri、异步文件 I/O 和 sandbox context 的读者。范围是 read/open/readBlock/close、write、目录和错误映射,不展开 sandbox helper 内部实现。读完后,你应能判断一次文件请求由谁拥有 handle、何时进入 sandbox,以及错误为何映射为不同 JSON-RPC code。

1. Handler分层 ​

源码位置:codex-rs/exec-server/src/server/file_system_handler.rs :: FileSystemHandler

rust
#[derive(Clone)]
pub(crate) struct FileSystemHandler {
    file_system: LocalFileSystem,
    file_reads: FileReadHandleManager,
}

pub(crate) async fn shutdown(&self) {
    self.file_reads.close_all().await;
}

handler 只拥有协议层资源:一次性 read/write 直接下沉到 LocalFileSystem;open 则把文件对象交给 handle manager,连接关闭时统一 close_all。

2. 路径选择 ​

LocalFileSystem::file_system_for 只在 sandbox context 明确要求 platform sandbox 时选择 sandboxed backend;否则使用 unsandboxed backend,但仍把 context 传给底层策略检查。open_file_for_read 走同一选择逻辑,因此不能把“流式”误解为绕过 sandbox。

源码位置:codex-rs/exec-server/src/local_file_system.rs :: file_system_for

rust
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))
    }
}

源码位置:codex-rs/exec-server/src/local_file_system.rs :: LocalFileSystem::open_file_for_read

rust
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

3. 读取句柄 ​

open 校验 handle ID 不超过 32 字节,FileReadHandleManager 最多保存每条连接 128 个打开文件;readBlock 校验块长度不超过 FILE_READ_CHUNK_SIZE(1 MiB),使用 spawn_blocking 和平台对应的 read_at/seek_read,close 是幂等的释放入口。读取出错时 manager 会主动移除该句柄。

源码位置:

  • codex-rs/exec-server/src/server/file_system_handler.rs :: FileSystemHandler::open、read_block、close
  • codex-rs/exec-server/src/file_read.rs :: FileReadHandleManager::open、read_block、close_all
rust
validate_file_read_handle_id(&params.handle_id)?;
let file = self
    .file_system
    .open_file_for_read(&params.path, params.sandbox.as_ref())
    .await
    .map_err(map_fs_error)?;
let handle_id = self.file_reads.open(params.handle_id, file)
    .await
    .map_err(map_fs_error)?;
Ok(FsOpenResponse { handle_id })

源码位置:codex-rs/exec-server/src/file_read.rs :: FileReadHandleManager::read_block

rust
validate_read_block_len(len)?;
let file = {
    let handles = self.handles.lock().await;
    handles
        .get(handle_id)
        .cloned()
        .ok_or_else(|| unknown_handle_error(handle_id))?
};
let result = tokio::task::spawn_blocking(move || read_block_at(&file, offset, len))
    .await
    .map_err(|error| io::Error::other(format!("file read task stopped unexpectedly: {error}")))?;
if result.is_err() {
    self.close(handle_id).await;
}

4. 一次性读写 ​

read_file 读取完整 bytes 后 base64 编码,并在 LocalFileSystem 中限制单次完整读取不超过 512 MiB;write_file 先解码 dataBase64,非法 base64 在触碰文件系统前返回 invalid request。目录读取限制 50,000 entries;递归 walk 还受深度 64、目录 10,000、条目 50,000 和响应 4 MiB 上限约束,避免单个 JSON response 失控。

源码位置:

  • codex-rs/exec-server/src/server/file_system_handler.rs :: FileSystemHandler::read_file、write_file、read_directory、walk
  • codex-rs/exec-server/src/local_file_system.rs :: DirectFileSystem::read_file、LocalFileSystem::walk
rust
let bytes = STANDARD.decode(params.data_base64).map_err(|err| {
    invalid_request(format!("fs/writeFile requires valid base64 dataBase64: {err}"))
})?;
self.file_system
    .write_file(&params.path, bytes, params.sandbox.as_ref())
    .await
    .map_err(map_fs_error)?;

源码位置:codex-rs/exec-server/src/local_file_system.rs :: DirectFileSystem::read_file

rust
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)

5. 错误映射 ​

filesystem io::ErrorKind 被映射为协议错误:NotFound → not_found,InvalidInput/PermissionDenied → invalid_request,其余 → internal_error。这样客户端可以区分路径不存在、请求不合法和服务端故障。

源码位置:codex-rs/exec-server/src/server/file_system_handler.rs :: map_fs_error

rust
fn map_fs_error(err: io::Error) -> JSONRPCErrorError {
    match err.kind() {
        io::ErrorKind::NotFound => not_found(err.to_string()),
        io::ErrorKind::InvalidInput | io::ErrorKind::PermissionDenied => {
            invalid_request(err.to_string())
        }
        _ => internal_error(err.to_string()),
    }
}

6. 验证 ​

6.1 文件回路 ​

exec-server filesystem tests 覆盖 read/write、metadata、directory、copy/remove 和 sandbox path;输入是 PathUri 与可选 sandbox context,断言返回 bytes、存在性或错误 code。

源码位置:codex-rs/exec-server/tests/file_system_unix.rs、file_stream.rs

text
cd codex-rs
cargo test -p codex-exec-server --test file_system_unix -- --test-threads=1
cargo test -p codex-exec-server --test file_stream -- --test-threads=1

6.2 handler边界 ​

handler 单测验证 platform sandbox streaming read 被拒绝、base64 非法输入被拒绝和目录数量上限;这些断言证明协议边界,不证明底层 sandbox helper 的系统调用细节。

源码位置:codex-rs/exec-server/src/server/file_system_handler.rs :: tests

text
cd codex-rs
cargo test -p codex-exec-server --lib server::file_system_handler::tests -- --test-threads=1

7. 源码排查 ​

text
rg -n "struct FileSystemHandler|validate_file_read_handle_id|map_fs_error" codex-rs/exec-server/src/server/file_system_handler.rs
rg -n "file_system_for|open_file_for_read|read_file\(|write_file\(" codex-rs/exec-server/src/local_file_system.rs
rg -n "file_stream|file_system_unix|FsRead|FsWrite" codex-rs/exec-server/tests

文件 RPC 主线是:handler 校验协议输入,LocalFileSystem 按 sandbox context 选择 backend,streaming read 由 handle manager 负责生命周期,一次性读写直接返回 base64/metadata,最后统一把 io error 映射为协议错误。下一篇将分析 exec-server 的网络 RPC。