use agentos_kernel::command_registry::CommandDriver;use agentos_kernel::fd_table::{ RecordLockType, FD_CLOEXEC, F_DUPFD, F_GETFD, F_GETFL, F_SETFD, F_SETFL, LOCK_EX, LOCK_NB, LOCK_SH, LOCK_UN, O_APPEND, O_CREAT, O_DIRECTORY, O_EXCL, O_NOFOLLOW, O_NONBLOCK, O_RDONLY, O_RDWR, O_TRUNC,};use agentos_kernel::kernel::{ ExecOptions, KernelVm, KernelVmConfig, OpenShellOptions, SpawnOptions, WaitPidFlags, WaitPidResult, SEEK_SET,};use agentos_kernel::mount_table::{MountOptions, MountTable};use agentos_kernel::permissions::Permissions;use agentos_kernel::pipe_manager::MAX_PIPE_BUFFER_BYTES;use agentos_kernel::process_table::{ProcessWaitEvent, SIGWINCH};use agentos_kernel::socket_table::SocketType;use agentos_kernel::vfs::{ MemoryFileSystem, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat, MAX_PATH_LENGTH,};use std::cell::{Cell, RefCell}; fn assert_kernel_error_code<T: std::fmt::Debug>( result: agentos_kernel::kernel::KernelResult<T>, expected: &str,) { let error = result.expect_err("operation should fail"); assert_eq!(error.code(), expected);} fn spawn_shell( kernel: &mut KernelVm<MemoryFileSystem>,) -> agentos_kernel::kernel::KernelProcessHandle { kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell")} fn spawn_shell_in<F: VirtualFileSystem + 'static>( kernel: &mut KernelVm<F>,) -> agentos_kernel::kernel::KernelProcessHandle { kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell")} fn assert_not_trivial_pattern(bytes: &[u8]) { assert!(bytes.iter().any(|byte| *byte != 0)); assert!( bytes.windows(2).any(|window| window[0] != window[1]), "random data should not collapse to a repeated byte" );} struct AtomicityProbeFileSystem { inner: RefCell<MemoryFileSystem>, exclusive_race_pending: Cell<bool>, append_race_pending: Cell<bool>, target_path: &'static str,} impl AtomicityProbeFileSystem { fn new(target_path: &'static str) -> Self { let mut inner = MemoryFileSystem::new(); inner .write_file(target_path, Vec::new()) .expect("seed append target"); Self { inner: RefCell::new(inner), exclusive_race_pending: Cell::new(false), append_race_pending: Cell::new(false), target_path, } } fn trigger_exclusive_race(&self) { self.inner .borrow_mut() .remove_file(self.target_path) .expect("clear target before exclusive race"); self.exclusive_race_pending.set(true); } fn trigger_append_race(&self) { self.inner .borrow_mut() .write_file(self.target_path, Vec::new()) .expect("reset target before append race"); self.append_race_pending.set(true); }} impl VirtualFileSystem for AtomicityProbeFileSystem { fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> { self.inner.borrow_mut().read_file(path) } fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> { self.inner.borrow_mut().read_dir(path) } fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> { self.inner.borrow_mut().read_dir_limited(path, max_entries) } fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> { self.inner.borrow_mut().read_dir_with_types(path) } fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> { let content = content.into(); if path == self.target_path { if self.exclusive_race_pending.replace(false) { self.inner .borrow_mut() .write_file(path, b"winner".to_vec()) .expect("inject competing exclusive creator"); } if self.append_race_pending.replace(false) { self.inner .borrow_mut() .write_file(path, b"RACE".to_vec()) .expect("inject competing append writer"); } } self.inner.borrow_mut().write_file(path, content) } fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> { if path == self.target_path && self.exclusive_race_pending.replace(false) { self.inner .borrow_mut() .write_file(path, b"winner".to_vec()) .expect("inject competing exclusive creator"); return Err(agentos_kernel::vfs::VfsError::new( "EEXIST", format!("file already exists, open '{path}'"), )); } self.inner.borrow_mut().create_file_exclusive(path, content) } fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> { if path == self.target_path && self.append_race_pending.replace(false) { self.inner .borrow_mut() .append_file(path, b"RACE".to_vec()) .expect("inject competing append writer"); } self.inner.borrow_mut().append_file(path, content) } fn create_dir(&mut self, path: &str) -> VfsResult<()> { self.inner.borrow_mut().create_dir(path) } fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> { self.inner.borrow_mut().mkdir(path, recursive) } fn exists(&self, path: &str) -> bool { if path == self.target_path && self.exclusive_race_pending.get() { return false; } self.inner.borrow().exists(path) } fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> { self.inner.borrow_mut().stat(path) } fn remove_file(&mut self, path: &str) -> VfsResult<()> { self.inner.borrow_mut().remove_file(path) } fn remove_dir(&mut self, path: &str) -> VfsResult<()> { self.inner.borrow_mut().remove_dir(path) } fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> { self.inner.borrow_mut().rename(old_path, new_path) } fn realpath(&self, path: &str) -> VfsResult<String> { self.inner.borrow().realpath(path) } fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> { self.inner.borrow_mut().symlink(target, link_path) } fn read_link(&self, path: &str) -> VfsResult<String> { self.inner.borrow().read_link(path) } fn lstat(&self, path: &str) -> VfsResult<VirtualStat> { self.inner.borrow().lstat(path) } fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> { self.inner.borrow_mut().link(old_path, new_path) } fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> { self.inner.borrow_mut().chmod(path, mode) } fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> { self.inner.borrow_mut().chown(path, uid, gid) } fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> { self.inner.borrow_mut().utimes(path, atime_ms, mtime_ms) } fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> { self.inner.borrow_mut().truncate(path, length) } fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> { self.inner.borrow_mut().pread(path, offset, length) }} #[test]fn kernel_fd_surface_supports_open_seek_positional_io_dup_and_dev_fd_views() { let mut config = KernelVmConfig::new("vm-api-fd"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .filesystem_mut() .write_file("/tmp/data.txt", b"hello".to_vec()) .expect("seed file"); let process = spawn_shell(&mut kernel); let fd = kernel .fd_open("shell", process.pid(), "/tmp/data.txt", O_RDWR, None) .expect("open existing file"); let created_fd = kernel .fd_open( "shell", process.pid(), "/tmp/created.txt", O_CREAT | O_RDWR, None, ) .expect("open created file"); kernel .fd_write("shell", process.pid(), created_fd, b"created") .expect("write created file"); kernel .fd_sync("shell", process.pid(), created_fd) .expect("sync regular file"); let directory_fd = kernel .fd_open("shell", process.pid(), "/tmp", 0, None) .expect("open directory for sync"); kernel .fd_sync("shell", process.pid(), directory_fd) .expect("sync directory"); let directory_entries = kernel .fd_read_dir_with_types("shell", process.pid(), directory_fd) .expect("read directory through fd"); assert!(directory_entries.iter().any(|entry| { entry.name == "data.txt" && !entry.is_directory && !entry.is_symbolic_link })); assert!(directory_entries.iter().any(|entry| { entry.name == "created.txt" && !entry.is_directory && !entry.is_symbolic_link })); assert_kernel_error_code( kernel.fd_read_dir_with_types("shell", process.pid(), created_fd), "ENOTDIR", ); assert_eq!( kernel .filesystem_mut() .read_file("/tmp/created.txt") .expect("read created file"), b"created".to_vec() ); let entries = kernel .dev_fd_read_dir("shell", process.pid()) .expect("list /dev/fd"); assert!(entries.contains(&String::from("0"))); assert!(entries.contains(&String::from("1"))); assert!(entries.contains(&fd.to_string())); assert!(entries.contains(&created_fd.to_string())); let pread = kernel .fd_pread("shell", process.pid(), fd, 2, 1) .expect("pread from offset"); assert_eq!(pread, b"el".to_vec()); assert_eq!( kernel .fd_seek("shell", process.pid(), fd, 4, SEEK_SET) .expect("seek to byte 4"), 4 ); let dup_fd = kernel .fd_dup("shell", process.pid(), fd) .expect("duplicate fd"); let dup_read = kernel .fd_read("shell", process.pid(), dup_fd, 1) .expect("read through dup"); assert_eq!(dup_read, b"o".to_vec()); kernel .fd_dup2("shell", process.pid(), fd, 20) .expect("dup2 onto target fd"); kernel .fd_seek("shell", process.pid(), 20, 0, SEEK_SET) .expect("seek dup2 target to start"); let full = kernel .fd_read("shell", process.pid(), fd, 5) .expect("read full file"); assert_eq!(full, b"hello".to_vec()); kernel .fd_pwrite("shell", process.pid(), fd, b"X", 1) .expect("pwrite at offset"); assert_eq!( kernel .filesystem_mut() .read_file("/tmp/data.txt") .expect("read updated file"), b"hXllo".to_vec() ); let file_stat = kernel .dev_fd_stat("shell", process.pid(), fd) .expect("stat regular file fd"); assert_eq!(file_stat.size, 5); assert_eq!(file_stat.blocks, 1); // Device ids are unique per filesystem instance; assert the fd stat // reports the same device as a direct path stat on the same filesystem. assert_eq!( file_stat.dev, kernel .filesystem_mut() .stat("/tmp/data.txt") .expect("stat updated file") .dev ); assert_eq!(file_stat.rdev, 0); assert!(!file_stat.is_directory); let (read_fd, write_fd) = kernel.open_pipe("shell", process.pid()).expect("open pipe"); assert_kernel_error_code(kernel.fd_sync("shell", process.pid(), read_fd), "EINVAL"); let (socket_fd, peer_fd) = kernel .fd_socketpair("shell", process.pid(), SocketType::Stream, false, false) .expect("open socketpair for sync validation"); assert_kernel_error_code(kernel.fd_sync("shell", process.pid(), socket_fd), "EINVAL"); kernel .fd_close("shell", process.pid(), socket_fd) .expect("close first socket"); kernel .fd_close("shell", process.pid(), peer_fd) .expect("close second socket"); kernel .fd_write("shell", process.pid(), write_fd, b"pipe-data") .expect("write pipe"); let dev_dup = kernel .fd_open( "shell", process.pid(), &format!("/dev/fd/{read_fd}"), 0, None, ) .expect("duplicate through /dev/fd"); let pipe_bytes = kernel .fd_read("shell", process.pid(), dev_dup, 32) .expect("read duplicated pipe fd"); assert_eq!(pipe_bytes, b"pipe-data".to_vec()); let pipe_stat = kernel .dev_fd_stat("shell", process.pid(), read_fd) .expect("stat pipe fd"); let write_pipe_stat = kernel .dev_fd_stat("shell", process.pid(), write_fd) .expect("stat pipe write fd"); assert_eq!(pipe_stat.mode, 0o010600); assert_eq!(pipe_stat.size, 0); assert_eq!(pipe_stat.blocks, 0); assert_ne!(pipe_stat.dev, 0); assert_eq!(pipe_stat.nlink, 1); assert_eq!(write_pipe_stat.dev, pipe_stat.dev); assert_eq!(write_pipe_stat.ino, pipe_stat.ino); assert!(!pipe_stat.is_directory); kernel .fd_close("shell", process.pid(), directory_fd) .expect("close directory fd"); assert_kernel_error_code( kernel.fd_read_dir_with_types("shell", process.pid(), directory_fd), "EBADF", ); kernel .fd_close("shell", process.pid(), created_fd) .expect("close synced file fd"); assert_kernel_error_code(kernel.fd_sync("shell", process.pid(), created_fd), "EBADF"); process.finish(0); kernel.waitpid(process.pid()).expect("wait for shell");} #[test]fn fd_open_directory_truncate_rejects_regular_file_without_truncating_it() { let mut config = KernelVmConfig::new("vm-open-directory-truncate"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .write_file("/regular", b"payload") .expect("seed regular file"); let process = spawn_shell(&mut kernel); assert_kernel_error_code( kernel.fd_open( "shell", process.pid(), "/regular", O_RDONLY | O_DIRECTORY | O_TRUNC, None, ), "ENOTDIR", ); assert_eq!( kernel.read_file("/regular").expect("read regular file"), b"payload" );} #[test]fn fd_open_nofollow_truncate_rejects_symlink_without_truncating_target() { let mut config = KernelVmConfig::new("vm-open-nofollow-truncate"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .write_file("/target", b"payload") .expect("seed target file"); kernel .symlink("/target", "/target-link") .expect("create target symlink"); let process = spawn_shell(&mut kernel); assert_kernel_error_code( kernel.fd_open( "shell", process.pid(), "/target-link", O_RDONLY | O_NOFOLLOW | O_TRUNC, None, ), "ELOOP", ); assert_eq!( kernel.read_file("/target").expect("read target file"), b"payload" );} #[test]fn fd_open_directory_create_rejects_missing_path_without_creating_it() { let mut config = KernelVmConfig::new("vm-open-directory-create"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let process = spawn_shell(&mut kernel); assert_kernel_error_code( kernel.fd_open( "shell", process.pid(), "/missing", O_RDONLY | O_DIRECTORY | O_CREAT, None,Lines 1–500 of 2045. Load more
use agentos_kernel::command_registry::CommandDriver; use agentos_kernel::fd_table::{ RecordLockType, FD_CLOEXEC, F_DUPFD, F_GETFD, F_GETFL, F_SETFD, F_SETFL, LOCK_EX, LOCK_NB, LOCK_SH, LOCK_UN, O_APPEND, O_CREAT, O_DIRECTORY, O_EXCL, O_NOFOLLOW, O_NONBLOCK, O_RDONLY, O_RDWR, O_TRUNC, }; use agentos_kernel::kernel::{ ExecOptions, KernelVm, KernelVmConfig, OpenShellOptions, SpawnOptions, WaitPidFlags, WaitPidResult, SEEK_SET, }; use agentos_kernel::mount_table::{MountOptions, MountTable}; use agentos_kernel::permissions::Permissions; use agentos_kernel::pipe_manager::MAX_PIPE_BUFFER_BYTES; use agentos_kernel::process_table::{ProcessWaitEvent, SIGWINCH}; use agentos_kernel::socket_table::SocketType; use agentos_kernel::vfs::{ MemoryFileSystem, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat, MAX_PATH_LENGTH, }; use std::cell::{Cell, RefCell}; fn assert_kernel_error_code<T: std::fmt::Debug>( result: agentos_kernel::kernel::KernelResult<T>, expected: &str, ) { let error = result.expect_err("operation should fail"); assert_eq!(error.code(), expected); } fn spawn_shell( kernel: &mut KernelVm<MemoryFileSystem>, ) -> agentos_kernel::kernel::KernelProcessHandle { kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell") } fn spawn_shell_in<F: VirtualFileSystem + 'static>( kernel: &mut KernelVm<F>, ) -> agentos_kernel::kernel::KernelProcessHandle { kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell") } fn assert_not_trivial_pattern(bytes: &[u8]) { assert!(bytes.iter().any(|byte| *byte != 0)); assert!( bytes.windows(2).any(|window| window[0] != window[1]), "random data should not collapse to a repeated byte" ); } struct AtomicityProbeFileSystem { inner: RefCell<MemoryFileSystem>, exclusive_race_pending: Cell<bool>, append_race_pending: Cell<bool>, target_path: &'static str, } impl AtomicityProbeFileSystem { fn new(target_path: &'static str) -> Self { let mut inner = MemoryFileSystem::new(); inner .write_file(target_path, Vec::new()) .expect("seed append target"); Self { inner: RefCell::new(inner), exclusive_race_pending: Cell::new(false), append_race_pending: Cell::new(false), target_path, } } fn trigger_exclusive_race(&self) { self.inner .borrow_mut() .remove_file(self.target_path) .expect("clear target before exclusive race"); self.exclusive_race_pending.set(true); } fn trigger_append_race(&self) { self.inner .borrow_mut() .write_file(self.target_path, Vec::new()) .expect("reset target before append race"); self.append_race_pending.set(true); } } impl VirtualFileSystem for AtomicityProbeFileSystem { fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> { self.inner.borrow_mut().read_file(path) } fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> { self.inner.borrow_mut().read_dir(path) } fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> { self.inner.borrow_mut().read_dir_limited(path, max_entries) } fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> { self.inner.borrow_mut().read_dir_with_types(path) } fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> { let content = content.into(); if path == self.target_path { if self.exclusive_race_pending.replace(false) { self.inner .borrow_mut() .write_file(path, b"winner".to_vec()) .expect("inject competing exclusive creator"); } if self.append_race_pending.replace(false) { self.inner .borrow_mut() .write_file(path, b"RACE".to_vec()) .expect("inject competing append writer"); } } self.inner.borrow_mut().write_file(path, content) } fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> { if path == self.target_path && self.exclusive_race_pending.replace(false) { self.inner .borrow_mut() .write_file(path, b"winner".to_vec()) .expect("inject competing exclusive creator"); return Err(agentos_kernel::vfs::VfsError::new( "EEXIST", format!("file already exists, open '{path}'"), )); } self.inner.borrow_mut().create_file_exclusive(path, content) } fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> { if path == self.target_path && self.append_race_pending.replace(false) { self.inner .borrow_mut() .append_file(path, b"RACE".to_vec()) .expect("inject competing append writer"); } self.inner.borrow_mut().append_file(path, content) } fn create_dir(&mut self, path: &str) -> VfsResult<()> { self.inner.borrow_mut().create_dir(path) } fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> { self.inner.borrow_mut().mkdir(path, recursive) } fn exists(&self, path: &str) -> bool { if path == self.target_path && self.exclusive_race_pending.get() { return false; } self.inner.borrow().exists(path) } fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> { self.inner.borrow_mut().stat(path) } fn remove_file(&mut self, path: &str) -> VfsResult<()> { self.inner.borrow_mut().remove_file(path) } fn remove_dir(&mut self, path: &str) -> VfsResult<()> { self.inner.borrow_mut().remove_dir(path) } fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> { self.inner.borrow_mut().rename(old_path, new_path) } fn realpath(&self, path: &str) -> VfsResult<String> { self.inner.borrow().realpath(path) } fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> { self.inner.borrow_mut().symlink(target, link_path) } fn read_link(&self, path: &str) -> VfsResult<String> { self.inner.borrow().read_link(path) } fn lstat(&self, path: &str) -> VfsResult<VirtualStat> { self.inner.borrow().lstat(path) } fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> { self.inner.borrow_mut().link(old_path, new_path) } fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> { self.inner.borrow_mut().chmod(path, mode) } fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> { self.inner.borrow_mut().chown(path, uid, gid) } fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> { self.inner.borrow_mut().utimes(path, atime_ms, mtime_ms) } fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> { self.inner.borrow_mut().truncate(path, length) } fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> { self.inner.borrow_mut().pread(path, offset, length) } } #[test] fn kernel_fd_surface_supports_open_seek_positional_io_dup_and_dev_fd_views() { let mut config = KernelVmConfig::new("vm-api-fd"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .filesystem_mut() .write_file("/tmp/data.txt", b"hello".to_vec()) .expect("seed file"); let process = spawn_shell(&mut kernel); let fd = kernel .fd_open("shell", process.pid(), "/tmp/data.txt", O_RDWR, None) .expect("open existing file"); let created_fd = kernel .fd_open( "shell", process.pid(), "/tmp/created.txt", O_CREAT | O_RDWR, None, ) .expect("open created file"); kernel .fd_write("shell", process.pid(), created_fd, b"created") .expect("write created file"); kernel .fd_sync("shell", process.pid(), created_fd) .expect("sync regular file"); let directory_fd = kernel .fd_open("shell", process.pid(), "/tmp", 0, None) .expect("open directory for sync"); kernel .fd_sync("shell", process.pid(), directory_fd) .expect("sync directory"); let directory_entries = kernel .fd_read_dir_with_types("shell", process.pid(), directory_fd) .expect("read directory through fd"); assert!(directory_entries.iter().any(|entry| { entry.name == "data.txt" && !entry.is_directory && !entry.is_symbolic_link })); assert!(directory_entries.iter().any(|entry| { entry.name == "created.txt" && !entry.is_directory && !entry.is_symbolic_link })); assert_kernel_error_code( kernel.fd_read_dir_with_types("shell", process.pid(), created_fd), "ENOTDIR", ); assert_eq!( kernel .filesystem_mut() .read_file("/tmp/created.txt") .expect("read created file"), b"created".to_vec() ); let entries = kernel .dev_fd_read_dir("shell", process.pid()) .expect("list /dev/fd"); assert!(entries.contains(&String::from("0"))); assert!(entries.contains(&String::from("1"))); assert!(entries.contains(&fd.to_string())); assert!(entries.contains(&created_fd.to_string())); let pread = kernel .fd_pread("shell", process.pid(), fd, 2, 1) .expect("pread from offset"); assert_eq!(pread, b"el".to_vec()); assert_eq!( kernel .fd_seek("shell", process.pid(), fd, 4, SEEK_SET) .expect("seek to byte 4"), 4 ); let dup_fd = kernel .fd_dup("shell", process.pid(), fd) .expect("duplicate fd"); let dup_read = kernel .fd_read("shell", process.pid(), dup_fd, 1) .expect("read through dup"); assert_eq!(dup_read, b"o".to_vec()); kernel .fd_dup2("shell", process.pid(), fd, 20) .expect("dup2 onto target fd"); kernel .fd_seek("shell", process.pid(), 20, 0, SEEK_SET) .expect("seek dup2 target to start"); let full = kernel .fd_read("shell", process.pid(), fd, 5) .expect("read full file"); assert_eq!(full, b"hello".to_vec()); kernel .fd_pwrite("shell", process.pid(), fd, b"X", 1) .expect("pwrite at offset"); assert_eq!( kernel .filesystem_mut() .read_file("/tmp/data.txt") .expect("read updated file"), b"hXllo".to_vec() ); let file_stat = kernel .dev_fd_stat("shell", process.pid(), fd) .expect("stat regular file fd"); assert_eq!(file_stat.size, 5); assert_eq!(file_stat.blocks, 1); // Device ids are unique per filesystem instance; assert the fd stat // reports the same device as a direct path stat on the same filesystem. assert_eq!( file_stat.dev, kernel .filesystem_mut() .stat("/tmp/data.txt") .expect("stat updated file") .dev ); assert_eq!(file_stat.rdev, 0); assert!(!file_stat.is_directory); let (read_fd, write_fd) = kernel.open_pipe("shell", process.pid()).expect("open pipe"); assert_kernel_error_code(kernel.fd_sync("shell", process.pid(), read_fd), "EINVAL"); let (socket_fd, peer_fd) = kernel .fd_socketpair("shell", process.pid(), SocketType::Stream, false, false) .expect("open socketpair for sync validation"); assert_kernel_error_code(kernel.fd_sync("shell", process.pid(), socket_fd), "EINVAL"); kernel .fd_close("shell", process.pid(), socket_fd) .expect("close first socket"); kernel .fd_close("shell", process.pid(), peer_fd) .expect("close second socket"); kernel .fd_write("shell", process.pid(), write_fd, b"pipe-data") .expect("write pipe"); let dev_dup = kernel .fd_open( "shell", process.pid(), &format!("/dev/fd/{read_fd}"), 0, None, ) .expect("duplicate through /dev/fd"); let pipe_bytes = kernel .fd_read("shell", process.pid(), dev_dup, 32) .expect("read duplicated pipe fd"); assert_eq!(pipe_bytes, b"pipe-data".to_vec()); let pipe_stat = kernel .dev_fd_stat("shell", process.pid(), read_fd) .expect("stat pipe fd"); let write_pipe_stat = kernel .dev_fd_stat("shell", process.pid(), write_fd) .expect("stat pipe write fd"); assert_eq!(pipe_stat.mode, 0o010600); assert_eq!(pipe_stat.size, 0); assert_eq!(pipe_stat.blocks, 0); assert_ne!(pipe_stat.dev, 0); assert_eq!(pipe_stat.nlink, 1); assert_eq!(write_pipe_stat.dev, pipe_stat.dev); assert_eq!(write_pipe_stat.ino, pipe_stat.ino); assert!(!pipe_stat.is_directory); kernel .fd_close("shell", process.pid(), directory_fd) .expect("close directory fd"); assert_kernel_error_code( kernel.fd_read_dir_with_types("shell", process.pid(), directory_fd), "EBADF", ); kernel .fd_close("shell", process.pid(), created_fd) .expect("close synced file fd"); assert_kernel_error_code(kernel.fd_sync("shell", process.pid(), created_fd), "EBADF"); process.finish(0); kernel.waitpid(process.pid()).expect("wait for shell"); } #[test] fn fd_open_directory_truncate_rejects_regular_file_without_truncating_it() { let mut config = KernelVmConfig::new("vm-open-directory-truncate"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .write_file("/regular", b"payload") .expect("seed regular file"); let process = spawn_shell(&mut kernel); assert_kernel_error_code( kernel.fd_open( "shell", process.pid(), "/regular", O_RDONLY | O_DIRECTORY | O_TRUNC, None, ), "ENOTDIR", ); assert_eq!( kernel.read_file("/regular").expect("read regular file"), b"payload" ); } #[test] fn fd_open_nofollow_truncate_rejects_symlink_without_truncating_target() { let mut config = KernelVmConfig::new("vm-open-nofollow-truncate"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .write_file("/target", b"payload") .expect("seed target file"); kernel .symlink("/target", "/target-link") .expect("create target symlink"); let process = spawn_shell(&mut kernel); assert_kernel_error_code( kernel.fd_open( "shell", process.pid(), "/target-link", O_RDONLY | O_NOFOLLOW | O_TRUNC, None, ), "ELOOP", ); assert_eq!( kernel.read_file("/target").expect("read target file"), b"payload" ); } #[test] fn fd_open_directory_create_rejects_missing_path_without_creating_it() { let mut config = KernelVmConfig::new("vm-open-directory-create"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let process = spawn_shell(&mut kernel); assert_kernel_error_code( kernel.fd_open( "shell", process.pid(), "/missing", O_RDONLY | O_DIRECTORY | O_CREAT, None,