Files
codex/codex-rs/exec-server/src/local_file_system_read.rs
T
Adam Perry @ OpenAI c39bfa4c8f Enable writable file streaming in exec-server (#50177)
## What changed

Advertise `fileWriteStreaming` and support `fs/open` with `mode: "replace"` to create or truncate a file for writing. Implement `fs/writeBlock` with explicit offsets and nonempty chunks of up to 1 MiB, rejecting ranges beyond the signed 64-bit file offset limit.

Apply write sandbox permissions when opening writable handles, including when full-disk reads are allowed. Keep `read` as the default open mode.

## Testing

Add integration coverage for positional writes, chunk and offset bounds, replacement truncation, read/write handle permissions, and sandbox write restrictions. Verify that duplicate handle IDs and exhausted handle capacity reject replacement opens without creating or truncating files.

GitOrigin-RevId: 696ee6437dd5f5b69d5a7667dddea0ff44472094
2026-10-02 04:25:52 +00:00

75 lines
2.3 KiB
Rust

//! Bounded blocking file reads that stop between chunks when their async caller is dropped.
use crate::FILE_READ_CHUNK_SIZE;
use crate::ReadFileOptions;
use crate::no_follow;
use crate::protocol::FsOpenMode;
use crate::regular_file;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::io;
use std::io::Read;
use tokio_util::sync::CancellationToken;
const MAX_READ_FILE_BYTES: u64 = 512 * 1024 * 1024;
pub(super) async fn read_file(
path: AbsolutePathBuf,
options: ReadFileOptions,
) -> io::Result<Vec<u8>> {
let cancelled = CancellationToken::new();
let _cancel_on_drop = cancelled.clone().drop_guard();
tokio::task::spawn_blocking(move || {
if cancelled.is_cancelled() {
return Err(io::Error::other("file read cancelled"));
}
let file = if options.follow_symlinks {
regular_file::open_sync(path.as_path(), FsOpenMode::Read)?
} else {
no_follow::open_file(path.as_path())?
};
let metadata = file.metadata()?;
if metadata.len() > MAX_READ_FILE_BYTES {
return Err(file_too_large_error());
}
let mut bytes = Vec::with_capacity(metadata.len() as usize);
CancellableReader {
reader: file.take(MAX_READ_FILE_BYTES + 1),
cancelled,
}
.read_to_end(&mut bytes)?;
if bytes.len() as u64 > MAX_READ_FILE_BYTES {
return Err(file_too_large_error());
}
Ok(bytes)
})
.await
.map_err(|error| io::Error::other(format!("filesystem task failed: {error}")))?
}
struct CancellableReader<R> {
reader: R,
cancelled: CancellationToken,
}
impl<R: Read> Read for CancellableReader<R> {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
if self.cancelled.is_cancelled() {
// read_to_end retries Interrupted errors, so cancellation must use another kind.
return Err(io::Error::other("file read cancelled"));
}
let count = bytes.len().min(FILE_READ_CHUNK_SIZE);
self.reader.read(&mut bytes[..count])
}
}
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"),
)
}
#[cfg(test)]
#[path = "local_file_system_read_tests.rs"]
mod tests;