diff --git a/host/src/component.rs b/host/src/component.rs index b0d4376d..9452a252 100644 --- a/host/src/component.rs +++ b/host/src/component.rs @@ -298,7 +298,7 @@ impl WasmComponentInstance { let component = component.hydrate(&engine)?; // resource/mem limiter - let mut limiter = Limiter::new(permissions.resource_limits.clone(), memory_pool); + let limiter = Limiter::new(permissions.resource_limits.clone(), memory_pool); // Create in-memory VFS let vfs_state = VfsState::new(permissions.vfs.clone(), limiter.clone()); diff --git a/host/src/limiter.rs b/host/src/limiter.rs index 9e7ed4eb..eb00eeb8 100644 --- a/host/src/limiter.rs +++ b/host/src/limiter.rs @@ -78,7 +78,7 @@ impl Limiter { } /// Grow memory usage. - pub(crate) fn grow(&mut self, bytes: usize) -> Result<(), GrowthError> { + pub(crate) fn grow(&self, bytes: usize) -> Result<(), GrowthError> { let mut self_guard = self .memory_reservation .lock() diff --git a/host/src/vfs/mod.rs b/host/src/vfs/mod.rs index 9e1e8baf..e83b51da 100644 --- a/host/src/vfs/mod.rs +++ b/host/src/vfs/mod.rs @@ -244,6 +244,11 @@ impl Allocation { requested: n, }) } + + /// Decrease allocation by given amount. + fn dec(&self, n: u64) { + self.n.fetch_sub(n, Ordering::SeqCst); + } } /// State for the virtual filesystem. @@ -425,7 +430,11 @@ impl<'a> VfsCtxView<'a> { /// Get node at given path. fn node_at(&self, res: Resource, path: &str) -> FsResult { let node = self.node(res)?; + self.get_node_from_start(path, node) + } + /// Get node at given path from given starting node. + fn get_node_from_start(&self, path: &str, node: SharedVfsNode) -> FsResult { let (is_root, directions) = PathTraversal::parse(path, &self.vfs_state.limits)?; let start = if is_root { @@ -456,7 +465,9 @@ impl<'a> VfsCtxView<'a> { .ok_or_else(|| FsError::trap(ErrorCode::Invalid))? { Ok(PathTraversal::Down(segment)) => segment, - _other @ (Ok(PathTraversal::Stay) | Ok(PathTraversal::Up) | Err(_)) => { + Ok(PathTraversal::Stay) => PathSegment::new(".", &self.vfs_state.limits)?, + Ok(PathTraversal::Up) => PathSegment::new("..", &self.vfs_state.limits)?, + Err(_) => { return Err(FsError::trap(ErrorCode::Invalid)); } }; @@ -720,24 +731,141 @@ impl<'a> filesystem::types::HostDescriptor for VfsCtxView<'a> { open_flags: OpenFlags, flags: DescriptorFlags, ) -> FsResult> { - // Handle write-like flags - if open_flags.intersects(OpenFlags::CREATE | OpenFlags::TRUNCATE) - || flags.intersects(DescriptorFlags::MUTATE_DIRECTORY | DescriptorFlags::WRITE) - { - return Err(FsError::trap(ErrorCode::ReadOnly)); + if path.is_empty() { + return Err(FsError::trap(ErrorCode::Invalid)); + } + + let base_desc = self.get_descriptor(self_)?; + let base_node = Arc::clone(&base_desc.node); + let base_flags = base_desc.flags; + + let create = open_flags.contains(OpenFlags::CREATE); + let directory = open_flags.contains(OpenFlags::DIRECTORY); + let exclusive = open_flags.contains(OpenFlags::EXCLUSIVE); + let truncate = open_flags.contains(OpenFlags::TRUNCATE); + + // Per POSIX: O_CREAT only creates regular files, not directories. + // https://github.com/WebAssembly/WASI/blob/184b0c0e9fd437e5e5601d6e327a28feddbbd7f7/proposals/filesystem/wit/types.wit#L145-L146 + // "If O_CREAT and O_DIRECTORY are set and the requested access mode is neither + // O_WRONLY nor O_RDWR, the result is unspecified." + // We choose to disallow this combination entirely. + if create && directory { + return Err(FsError::trap(ErrorCode::Invalid)); } - // get node - let node = self.node_at(self_, &path)?; + if directory && flags.contains(DescriptorFlags::WRITE) { + // Per POSIX: "O_DIRECTORY: "If path resolves to a non-directory file, fail and set errno to [ENOTDIR]." + return Err(FsError::trap(ErrorCode::IsDirectory)); + } + + // Try to resolve the path to an existing node + let existing = self.get_node_from_start(&path, Arc::clone(&base_node)); + + let node = match (existing, create, directory, exclusive, truncate) { + (Ok(node), true, _, false, false) => node, // Per POSIX: "If the file exists, O_CREAT has no effect except as noted under O_EXCL below. + (Ok(_), true, _, true, _) => { + // Per POSIX: "O_CREAT and O_EXCL are set, open() shall fail if the file exists" + return Err(FsError::trap(ErrorCode::Exist)); + } + (Ok(node), false, true, false, false) => { + // Per POSIX: "O_DIRECTORY: "If path resolves to a non-directory file, fail and set errno to [ENOTDIR]." + let guard = node.read().unwrap(); + if !matches!(guard.kind, VfsNodeKind::Directory { .. }) { + return Err(FsError::trap(ErrorCode::NotDirectory)); + } + drop(guard); + node + } + (Ok(node), _, _, _, true) => { + let mut guard = node.write().unwrap(); + match &mut guard.kind { + VfsNodeKind::File { content } => { + // Per POSIX: "The result of using O_TRUNC without either O_RDWR + // or O_WRONLY is undefined." We allow it but it's a no-op + // unless write permission is granted. + if flags.contains(DescriptorFlags::WRITE) { + content.clear(); + } + } + VfsNodeKind::Directory { .. } => { + // Per POSIX: O_TRUNC "shall have no effect on... terminal device files. + // Its effect on other file types is implementation-defined." + // POSIX also says [EISDIR] for "The named file is a directory and + // oflag includes O_WRONLY or O_RDWR" + return Err(FsError::trap(ErrorCode::IsDirectory)); + } + } + drop(guard); + node + } + (Ok(node), _, _, _, _) => node, + (Err(_), false, _, _, _) => { + // "If O_CREAT is not set and the file does not exist, open() + // shall fail and set errno to [ENOENT]." + return Err(FsError::trap(ErrorCode::NoEntry)); + } + (Err(_), true, _, _, _) => { + // "If O_CREAT is set and the file does not exist, it shall be + // created as a regular file with permissions" + if !base_flags.contains(DescriptorFlags::MUTATE_DIRECTORY) { + return Err(FsError::trap(ErrorCode::ReadOnly)); + } + + let (parent_node, name) = + self.parent_node_and_name(Arc::clone(&base_node), &path)?; + + let new_file = Arc::new(RwLock::new(VfsNode { + kind: VfsNodeKind::File { + content: Vec::new(), + }, + parent: Some(Arc::downgrade(&parent_node)), + })); + + // Account for resource usage + self.vfs_state + .inodes_allocation + .inc(1) + .map_err(FsError::trap)?; + let growth = name.len() + std::mem::size_of_val(&new_file); + self.vfs_state.limiter.grow(growth).map_err(|_| { + // Rollback inode allocation since we failed to account for + // the new file's name and node size + self.vfs_state.inodes_allocation.dec(1); + FsError::trap(ErrorCode::InsufficientMemory) + })?; + + // Insert the new file into the parent directory + match &mut parent_node.write().unwrap().kind { + VfsNodeKind::File { .. } => { + // Parent is a file, not a directory + // Per POSIX [ENOTDIR]: "A component of the path prefix names an + // existing file that is neither a directory nor a symbolic link to a directory" + return Err(FsError::trap(ErrorCode::NotDirectory)); + } + VfsNodeKind::Directory { children } => { + match children.entry(name) { + Entry::Vacant(entry) => { + entry.insert(Arc::clone(&new_file)); + } + Entry::Occupied(_) => { + // Race condition: file was created between our check and insert + // Per POSIX with O_EXCL, this should return EEXIST + // Without O_EXCL, we could open the existing file, but for + // simplicity we return EEXIST since this is a race condition + return Err(FsError::trap(ErrorCode::Exist)); + } + } + } + } - // Create descriptor - let new_desc = VfsDescriptor { node, flags }; + new_file + } + }; let res = self .table - .push(new_desc) + .push(VfsDescriptor { node, flags }) .map_err(|_| FsError::trap(ErrorCode::InsufficientMemory))?; - // Convert Resource to Resource Ok(res.cast()) } @@ -925,18 +1053,119 @@ mod tests { use super::*; use crate::limiter::{Limiter, StaticResourceLimits}; - /// Create a test VfsCtxView with default limits - fn create_test_vfs() -> (ResourceTable, VfsState) { - let limits = VfsLimits::default(); - let pool: Arc = Arc::new(UnboundedMemoryPool::default()); - let limiter = Limiter::new(StaticResourceLimits::default(), &pool); - let vfs_state = VfsState::new(limits, limiter); - let table = ResourceTable::new(); + /// Parameters for creating a test VFS context. + struct VfsTestParams { + /// Maximum number of inodes allowed. + inodes: u64, + /// Maximum path length. + max_path_length: u64, + /// Maximum path segment size. + max_path_segment_size: u64, + /// Memory pool size in bytes. If `None`, uses unbounded memory. + memory_pool_bytes: Option, + /// Static resource limits for the limiter. + static_limits: StaticResourceLimits, + } + + impl Default for VfsTestParams { + fn default() -> Self { + Self { + inodes: 100, + max_path_length: 255, + max_path_segment_size: 100, + memory_pool_bytes: None, + static_limits: StaticResourceLimits::default(), + } + } + } + + impl VfsTestParams { + /// Create params with a specific inode limit. + fn with_inodes(mut self, inodes: u64) -> Self { + self.inodes = inodes; + self + } + + /// Create params with a specific memory pool size. + fn with_memory_pool_bytes(mut self, bytes: usize) -> Self { + self.memory_pool_bytes = Some(bytes); + self + } + + /// Create params for limited space tests (very constrained resources). + fn with_limited_space(mut self, bytes: usize) -> Self { + self.memory_pool_bytes = Some(bytes); + self.static_limits = StaticResourceLimits { + n_elements_per_table: 1, + n_instances: 1, + n_tables: 1, + n_memories: 1, + }; + self + } + + /// Build the VFS state and resource table from these parameters. + fn build(self) -> (ResourceTable, VfsState) { + let limits = VfsLimits { + inodes: self.inodes, + max_path_length: self.max_path_length, + max_path_segment_size: self.max_path_segment_size, + }; + + let pool: Arc = match self.memory_pool_bytes { + Some(bytes) => Arc::new(GreedyMemoryPool::new(bytes)), + None => Arc::new(UnboundedMemoryPool::default()), + }; + + let limiter = Limiter::new(self.static_limits, &pool); + let vfs_state = VfsState::new(limits, limiter); + let table = ResourceTable::new(); + (table, vfs_state) + } + } + + /// Assert that a result is an error with a specific ErrorCode. + #[track_caller] + fn assert_error_code(result: FsResult, expected: ErrorCode) { + assert!(result.is_err(), "Expected error, got {:?}", result); + let err = result.unwrap_err(); + let actual = err.downcast_ref().expect("Error should contain ErrorCode"); + assert_eq!(*actual, expected, "Error code mismatch"); + } + + /// Assert that a node is a file with specific content. + #[track_caller] + fn assert_file_content(node: &SharedVfsNode, expected: &[u8]) { + let guard = node.read().unwrap(); + match &guard.kind { + VfsNodeKind::File { content } => { + assert_eq!(content.as_slice(), expected, "File content mismatch"); + } + VfsNodeKind::Directory { .. } => { + panic!("Expected file, got directory"); + } + } + } - (table, vfs_state) + /// Assert that a node is an empty file. + #[track_caller] + fn assert_empty_file(node: &SharedVfsNode) { + assert_file_content(node, &[]); } - /// Create a test descriptor with the given flags + /// Assert that a node is a directory. + #[track_caller] + fn assert_is_directory(node: &SharedVfsNode) { + let guard = node.read().unwrap(); + match &guard.kind { + VfsNodeKind::Directory { .. } => {} + VfsNodeKind::File { .. } => { + panic!("Expected directory, got file"); + } + } + } + + /// Create a test descriptor with the given flags. fn create_test_descriptor( ctx: &mut VfsCtxView<'_>, flags: DescriptorFlags, @@ -949,58 +1178,142 @@ mod tests { res.cast() } + /// Helper to create a file in the VFS for testing. + async fn create_test_file_via_open(ctx: &mut VfsCtxView<'_>, name: &str) { + let desc = create_test_descriptor( + ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); + let _ = ctx + .open_at( + desc, + PathFlags::empty(), + name.to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) + .await + .expect("file creation should succeed"); + } + + /// Helper to create a directory in the VFS for testing. + async fn create_test_directory(ctx: &mut VfsCtxView<'_>, name: &str) { + let desc = create_test_descriptor( + ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); + ctx.create_directory_at(desc, name.to_string()) + .await + .expect("directory creation should succeed"); + } + + /// Helper to create a file with content in the VFS. + async fn create_file_with_content( + ctx: &mut VfsCtxView<'_>, + name: &str, + content: Vec, + ) -> SharedVfsNode { + let desc = create_test_descriptor( + ctx, + DescriptorFlags::READ | DescriptorFlags::WRITE | DescriptorFlags::MUTATE_DIRECTORY, + ); + ctx.open_at( + desc, + PathFlags::empty(), + name.to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::WRITE, + ) + .await + .unwrap(); + + let desc = create_test_descriptor(ctx, DescriptorFlags::READ); + let node = ctx.node_at(desc, name).unwrap(); + + { + let mut guard = node.write().unwrap(); + if let VfsNodeKind::File { content: c } = &mut guard.kind { + *c = content; + } + } + + node + } + + // ==================== create_directory_at tests ==================== + #[tokio::test] async fn test_create_directory_readonly_descriptor_fails() { - let (mut table, mut vfs_state) = create_test_vfs(); + let (mut table, mut vfs_state) = VfsTestParams::default().build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, }; - // Create descriptor with READ flags only (no MUTATE_DIRECTORY) let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); - - // Attempt to create directory should fail with ReadOnly error let result = ctx.create_directory_at(desc, "testdir".to_string()).await; - assert!(result.is_err()); - - let err = result.unwrap_err(); - assert_eq!(*err.downcast_ref().unwrap(), ErrorCode::ReadOnly); + assert_error_code(result, ErrorCode::ReadOnly); } #[tokio::test] async fn test_create_directory_already_exists_fails() { - let (mut table, mut vfs_state) = create_test_vfs(); + let (mut table, mut vfs_state) = VfsTestParams::default().build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, }; + create_test_directory(&mut ctx, "testdir").await; + let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx.create_directory_at(desc, "testdir".to_string()).await; + assert_error_code(result, ErrorCode::Exist); + } + + #[tokio::test] + async fn test_create_directory_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; - // First creation should succeed + let desc = create_test_descriptor( + &mut ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); let result = ctx.create_directory_at(desc, "testdir".to_string()).await; assert!(result.is_ok()); + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let node = ctx.node_at(desc, "testdir").unwrap(); + assert_is_directory(&node); + } + + #[tokio::test] + async fn test_create_directory_insufficient_inodes_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().with_inodes(1).build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_directory(&mut ctx, "testdir").await; + let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); - - // Second creation should fail with Exist error - let result = ctx.create_directory_at(desc, "testdir".to_string()).await; + let result = ctx.create_directory_at(desc, "testdir2".to_string()).await; assert!(result.is_err()); - - let err = result.unwrap_err(); - assert_eq!(*err.downcast_ref().unwrap(), ErrorCode::Exist); } #[tokio::test] - async fn test_create_directory_success() { - let (mut table, mut vfs_state) = create_test_vfs(); + async fn test_create_directory_insufficient_space_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().with_limited_space(2).build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, @@ -1010,168 +1323,606 @@ mod tests { &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .create_directory_at( + desc, + "/very_long_directory_name_with_a_bunch_of_limits".to_string(), + ) + .await; + assert_error_code(result, ErrorCode::InsufficientMemory); + } - let result = ctx.create_directory_at(desc, "testdir".to_string()).await; + #[tokio::test] + async fn test_create_directory_nested_path_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_directory(&mut ctx, "parent").await; + + let desc = create_test_descriptor( + &mut ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); + let result = ctx + .create_directory_at(desc, "parent/child".to_string()) + .await; assert!(result.is_ok()); + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + assert!(ctx.node_at(desc, "parent").is_ok()); + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + assert!(ctx.node_at(desc, "parent/child").is_ok()); + } + + #[tokio::test] + async fn test_create_directory_invalid_parent_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .create_directory_at(desc, "nonexistent/child".to_string()) + .await; + assert_error_code(result, ErrorCode::NoEntry); + } - // Verify the directory was created by checking it exists - let node_result = ctx.node_at(desc, "testdir"); - assert!(node_result.is_ok()); + // ==================== open_at tests ==================== - let node = node_result.unwrap(); - let node_guard = node.read().unwrap(); - match &node_guard.kind { - VfsNodeKind::Directory { .. } => { - // Success - it's a directory - } - VfsNodeKind::File { .. } => { - panic!("Expected directory, got file"); - } - } + #[tokio::test] + async fn test_open_at_directory_flag_on_nonexistent_path_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "nonexistent".to_string(), + OpenFlags::DIRECTORY, + DescriptorFlags::READ, + ) + .await; + assert_error_code(result, ErrorCode::NoEntry); } #[tokio::test] - async fn test_create_directory_insufficient_inodes_fails() { - // Create VFS with very limited inodes (1 inode, does not include root) - let limits = VfsLimits { - inodes: 1, - max_path_length: 255, - max_path_segment_size: 50, + async fn test_open_at_directory_flag_on_file_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, }; - let pool: Arc = Arc::new(UnboundedMemoryPool::default()); - let limiter = Limiter::new(StaticResourceLimits::default(), &pool); - let mut vfs_state = VfsState::new(limits, limiter); - let mut table = ResourceTable::new(); + create_test_file_via_open(&mut ctx, "afile").await; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "afile".to_string(), + OpenFlags::DIRECTORY, + DescriptorFlags::READ, + ) + .await; + assert_error_code(result, ErrorCode::NotDirectory); + } + + #[tokio::test] + async fn test_open_at_directory_flag_on_directory_succeeds() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_directory(&mut ctx, "adir").await; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "adir".to_string(), + OpenFlags::DIRECTORY, + DescriptorFlags::READ, + ) + .await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_open_at_create_and_directory_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, }; - // Create descriptor with proper flags let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "newdir".to_string(), + OpenFlags::CREATE | OpenFlags::DIRECTORY, + DescriptorFlags::READ, + ) + .await; + assert_error_code(result, ErrorCode::Invalid); + } - // Directory creation should fail due to insufficient inodes - let result = ctx.create_directory_at(desc, "testdir".to_string()).await; + #[tokio::test] + async fn test_open_at_create_exists_exclusive_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_file_via_open(&mut ctx, "existingfile").await; + + let desc = create_test_descriptor( + &mut ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "existingfile".to_string(), + OpenFlags::CREATE | OpenFlags::EXCLUSIVE, + DescriptorFlags::READ, + ) + .await; + assert_error_code(result, ErrorCode::Exist); + } + + #[tokio::test] + async fn test_open_at_create_exists_not_exclusive_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_file_via_open(&mut ctx, "existingfile").await; + + let desc = create_test_descriptor( + &mut ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "existingfile".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ, + ) + .await; assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_open_at_create_no_mutate_permission_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "newfile".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ, + ) + .await; + assert_error_code(result, ErrorCode::ReadOnly); + } + + #[tokio::test] + async fn test_open_at_create_new_file_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "newfile".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) + .await; + assert!(result.is_ok()); - let result = ctx.create_directory_at(desc, "testdir2".to_string()).await; - assert!(result.is_err()); + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let node = ctx.node_at(desc, "newfile").unwrap(); + assert_empty_file(&node); } #[tokio::test] - async fn test_create_directory_insufficient_space_fails() { - // Create VFS with very limited space (10 bytes) - let limits = VfsLimits::default(); - let pool: Arc = Arc::new(GreedyMemoryPool::new(2)); - let static_limits = StaticResourceLimits { - n_elements_per_table: 1, - n_instances: 1, - n_tables: 1, - n_memories: 1, + async fn test_open_at_create_parent_is_file_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, }; - let limiter = Limiter::new(static_limits, &pool); - let mut vfs_state = VfsState::new(limits, limiter); - let mut table = ResourceTable::new(); + create_test_file_via_open(&mut ctx, "afile").await; + + let desc = create_test_descriptor( + &mut ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "afile/child".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) + .await; + assert_error_code(result, ErrorCode::NotDirectory); + } + + #[tokio::test] + async fn test_open_at_create_exclusive_new_file_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, }; - // Create descriptor with proper flags let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "brandnewfile".to_string(), + OpenFlags::CREATE | OpenFlags::EXCLUSIVE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) + .await; + assert!(result.is_ok()); + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let node = ctx.node_at(desc, "brandnewfile"); + assert!(node.is_ok()); + } + + #[tokio::test] + async fn test_open_at_create_insufficient_inodes_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().with_inodes(0).build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; - // Directory creation should fail due to insufficient space for name + let desc = create_test_descriptor( + &mut ctx, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ); let result = ctx - .create_directory_at( + .open_at( desc, - "very_long_directory_name_with_a_bunch_of_limits".to_string(), + PathFlags::empty(), + "newfile".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ) .await; assert!(result.is_err()); } #[tokio::test] - async fn test_create_directory_nested_path_success() { - let (mut table, mut vfs_state) = create_test_vfs(); + async fn test_open_at_create_insufficient_memory_for_name_fails() { + let (mut table, mut vfs_state) = + VfsTestParams::default().with_memory_pool_bytes(10).build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, }; - // Create descriptor with proper flags let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "long_filename_to_exceed_limits".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) + .await; + assert_error_code(result, ErrorCode::InsufficientMemory); + } + + #[tokio::test] + async fn test_open_at_no_create_nonexistent_file_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "nonexistent".to_string(), + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .await; + assert_error_code(result, ErrorCode::NoEntry); + } + + #[tokio::test] + async fn test_open_at_truncate_file_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + let content = vec![1, 2, 3, 4, 5]; + let file_node = create_file_with_content(&mut ctx, "testfile", content).await; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ | DescriptorFlags::WRITE); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "testfile".to_string(), + OpenFlags::TRUNCATE, + DescriptorFlags::READ | DescriptorFlags::WRITE, + ) + .await; + assert!(result.is_ok()); + + assert_empty_file(&file_node); + } + + #[tokio::test] + async fn test_open_at_truncate_directory_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_directory(&mut ctx, "adir").await; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ | DescriptorFlags::WRITE); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "adir".to_string(), + OpenFlags::TRUNCATE, + DescriptorFlags::READ | DescriptorFlags::WRITE, + ) + .await; + assert_error_code(result, ErrorCode::IsDirectory); + } + + #[tokio::test] + async fn test_open_at_existing_file_no_flags_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_file_via_open(&mut ctx, "existingfile").await; - // First create parent directory - let result = ctx.create_directory_at(desc, "parent".to_string()).await; + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "existingfile".to_string(), + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .await; assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_open_at_create_with_nested_path_success() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_directory(&mut ctx, "subdir").await; let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); - - // Then create child directory using relative path let result = ctx - .create_directory_at(desc, "parent/child".to_string()) + .open_at( + desc, + PathFlags::empty(), + "subdir/newfile".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) .await; assert!(result.is_ok()); - // Verify both directories were created + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let node = ctx.node_at(desc, "subdir/newfile"); + assert!(node.is_ok()); + } + + #[tokio::test] + async fn test_open_at_create_nonexistent_parent_path_fails() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + let desc = create_test_descriptor( &mut ctx, DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, ); - assert!(ctx.node_at(desc, "parent").is_ok()); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "nonexistent/newfile".to_string(), + OpenFlags::CREATE, + DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + ) + .await; + assert_error_code(result, ErrorCode::NoEntry); + } + + #[tokio::test] + async fn test_open_at_create_and_truncate_new_file() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; let desc = create_test_descriptor( &mut ctx, - DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + DescriptorFlags::READ | DescriptorFlags::WRITE | DescriptorFlags::MUTATE_DIRECTORY, ); - assert!(ctx.node_at(desc, "parent/child").is_ok()); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "newfile".to_string(), + OpenFlags::CREATE | OpenFlags::TRUNCATE, + DescriptorFlags::READ | DescriptorFlags::WRITE, + ) + .await; + assert!(result.is_ok()); + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let node = ctx.node_at(desc, "newfile").unwrap(); + assert_empty_file(&node); } #[tokio::test] - async fn test_create_directory_invalid_parent_fails() { - let (mut table, mut vfs_state) = create_test_vfs(); + async fn test_open_at_create_and_truncate_existing_file() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); let mut ctx = VfsCtxView { table: &mut table, vfs_state: &mut vfs_state, }; - // Create descriptor with proper flags + let content = vec![1, 2, 3, 4, 5]; + let file_node = create_file_with_content(&mut ctx, "existingfile", content).await; + let desc = create_test_descriptor( &mut ctx, - DescriptorFlags::READ | DescriptorFlags::MUTATE_DIRECTORY, + DescriptorFlags::READ | DescriptorFlags::WRITE | DescriptorFlags::MUTATE_DIRECTORY, ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "existingfile".to_string(), + OpenFlags::TRUNCATE, + DescriptorFlags::READ | DescriptorFlags::WRITE, + ) + .await; + assert!(result.is_ok()); - // Try to create directory with non-existent parent + assert_empty_file(&file_node); + } + + #[tokio::test] + async fn test_open_at_truncate_without_write_permission_no_truncate() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + let content = vec![1, 2, 3, 4, 5]; + let file_node = create_file_with_content(&mut ctx, "testfile", content.clone()).await; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); let result = ctx - .create_directory_at(desc, "nonexistent/child".to_string()) + .open_at( + desc, + PathFlags::empty(), + "testfile".to_string(), + OpenFlags::TRUNCATE, + DescriptorFlags::READ, + ) .await; - assert!(result.is_err()); + assert!(result.is_ok()); - let err = result.unwrap_err(); - assert_eq!(*err.downcast_ref().unwrap(), ErrorCode::NoEntry); + assert_file_content(&file_node, &content); + } + + #[tokio::test] + async fn test_open_at_exclusive_without_create_is_ignored() { + let (mut table, mut vfs_state) = VfsTestParams::default().build(); + let mut ctx = VfsCtxView { + table: &mut table, + vfs_state: &mut vfs_state, + }; + + create_test_file_via_open(&mut ctx, "existingfile").await; + + let desc = create_test_descriptor(&mut ctx, DescriptorFlags::READ); + let result = ctx + .open_at( + desc, + PathFlags::empty(), + "existingfile".to_string(), + OpenFlags::EXCLUSIVE, + DescriptorFlags::READ, + ) + .await; + assert!(result.is_ok()); } } diff --git a/host/src/vfs/path.rs b/host/src/vfs/path.rs index 70794da5..bc8ea2d7 100644 --- a/host/src/vfs/path.rs +++ b/host/src/vfs/path.rs @@ -26,7 +26,7 @@ impl PathSegment { /// - the segment MUST NOT contain a NULL character /// - the segment MUST NOT contain a slash `/` character /// - the segment MUST NOT be empty - fn new(s: &str, limit: &VfsLimits) -> Result { + pub(crate) fn new(s: &str, limit: &VfsLimits) -> Result { assert!(!s.contains('\0')); assert!(!s.contains('/')); assert!(!s.is_empty()); diff --git a/host/tests/integration_tests/evil/fs.rs b/host/tests/integration_tests/evil/fs.rs index 73e07779..582eced8 100644 --- a/host/tests/integration_tests/evil/fs.rs +++ b/host/tests/integration_tests/evil/fs.rs @@ -91,114 +91,114 @@ async fn test_copy() { +-------------+-------------+-------------------------------------------------+ | from | to | output | +-------------+-------------+-------------------------------------------------+ - | | | ERR: Read-only file system (os error 69) | - | | . | ERR: Read-only file system (os error 69) | - | | .. | ERR: Read-only file system (os error 69) | - | | / | ERR: Read-only file system (os error 69) | - | | /bin | ERR: Read-only file system (os error 69) | - | | /boot | ERR: Read-only file system (os error 69) | - | | /dev | ERR: Read-only file system (os error 69) | - | | /etc | ERR: Read-only file system (os error 69) | - | | /etc/group | ERR: Read-only file system (os error 69) | - | | /etc/passwd | ERR: Read-only file system (os error 69) | - | | /etc/shadow | ERR: Read-only file system (os error 69) | - | | /home | ERR: Read-only file system (os error 69) | - | | /lib | ERR: Read-only file system (os error 69) | - | | /lib64 | ERR: Read-only file system (os error 69) | - | | /opt | ERR: Read-only file system (os error 69) | - | | /proc | ERR: Read-only file system (os error 69) | - | | /proc/self | ERR: Read-only file system (os error 69) | - | | /root | ERR: Read-only file system (os error 69) | - | | /run | ERR: Read-only file system (os error 69) | - | | /sbin | ERR: Read-only file system (os error 69) | - | | /srv | ERR: Read-only file system (os error 69) | - | | /sys | ERR: Read-only file system (os error 69) | - | | /tmp | ERR: Read-only file system (os error 69) | - | | /usr | ERR: Read-only file system (os error 69) | - | | /var | ERR: Read-only file system (os error 69) | + | | | ERR: Is a directory (os error 31) | + | | . | ERR: Is a directory (os error 31) | + | | .. | ERR: Is a directory (os error 31) | + | | / | ERR: Is a directory (os error 31) | + | | /bin | ERR: Bad file descriptor (os error 8) | + | | /boot | ERR: Bad file descriptor (os error 8) | + | | /dev | ERR: Bad file descriptor (os error 8) | + | | /etc | ERR: Bad file descriptor (os error 8) | + | | /etc/group | ERR: No such file or directory (os error 44) | + | | /etc/passwd | ERR: No such file or directory (os error 44) | + | | /etc/shadow | ERR: No such file or directory (os error 44) | + | | /home | ERR: Bad file descriptor (os error 8) | + | | /lib | ERR: Bad file descriptor (os error 8) | + | | /lib64 | ERR: Bad file descriptor (os error 8) | + | | /opt | ERR: Bad file descriptor (os error 8) | + | | /proc | ERR: Bad file descriptor (os error 8) | + | | /proc/self | ERR: No such file or directory (os error 44) | + | | /root | ERR: Bad file descriptor (os error 8) | + | | /run | ERR: Bad file descriptor (os error 8) | + | | /sbin | ERR: Bad file descriptor (os error 8) | + | | /srv | ERR: Bad file descriptor (os error 8) | + | | /sys | ERR: Bad file descriptor (os error 8) | + | | /tmp | ERR: Bad file descriptor (os error 8) | + | | /usr | ERR: Bad file descriptor (os error 8) | + | | /var | ERR: Bad file descriptor (os error 8) | | | \0 | ERR: file name contained an unexpected NUL byte | - | | /x/.. | ERR: Read-only file system (os error 69) | - | . | | ERR: Read-only file system (os error 69) | - | . | . | ERR: Read-only file system (os error 69) | - | . | .. | ERR: Read-only file system (os error 69) | - | . | / | ERR: Read-only file system (os error 69) | - | . | /bin | ERR: Read-only file system (os error 69) | - | . | /boot | ERR: Read-only file system (os error 69) | - | . | /dev | ERR: Read-only file system (os error 69) | - | . | /etc | ERR: Read-only file system (os error 69) | - | . | /etc/group | ERR: Read-only file system (os error 69) | - | . | /etc/passwd | ERR: Read-only file system (os error 69) | - | . | /etc/shadow | ERR: Read-only file system (os error 69) | - | . | /home | ERR: Read-only file system (os error 69) | - | . | /lib | ERR: Read-only file system (os error 69) | - | . | /lib64 | ERR: Read-only file system (os error 69) | - | . | /opt | ERR: Read-only file system (os error 69) | - | . | /proc | ERR: Read-only file system (os error 69) | - | . | /proc/self | ERR: Read-only file system (os error 69) | - | . | /root | ERR: Read-only file system (os error 69) | - | . | /run | ERR: Read-only file system (os error 69) | - | . | /sbin | ERR: Read-only file system (os error 69) | - | . | /srv | ERR: Read-only file system (os error 69) | - | . | /sys | ERR: Read-only file system (os error 69) | - | . | /tmp | ERR: Read-only file system (os error 69) | - | . | /usr | ERR: Read-only file system (os error 69) | - | . | /var | ERR: Read-only file system (os error 69) | + | | /x/.. | ERR: No such file or directory (os error 44) | + | . | | ERR: Is a directory (os error 31) | + | . | . | ERR: Is a directory (os error 31) | + | . | .. | ERR: Is a directory (os error 31) | + | . | / | ERR: Is a directory (os error 31) | + | . | /bin | ERR: Bad file descriptor (os error 8) | + | . | /boot | ERR: Bad file descriptor (os error 8) | + | . | /dev | ERR: Bad file descriptor (os error 8) | + | . | /etc | ERR: Bad file descriptor (os error 8) | + | . | /etc/group | ERR: No such file or directory (os error 44) | + | . | /etc/passwd | ERR: No such file or directory (os error 44) | + | . | /etc/shadow | ERR: No such file or directory (os error 44) | + | . | /home | ERR: Bad file descriptor (os error 8) | + | . | /lib | ERR: Bad file descriptor (os error 8) | + | . | /lib64 | ERR: Bad file descriptor (os error 8) | + | . | /opt | ERR: Bad file descriptor (os error 8) | + | . | /proc | ERR: Bad file descriptor (os error 8) | + | . | /proc/self | ERR: No such file or directory (os error 44) | + | . | /root | ERR: Bad file descriptor (os error 8) | + | . | /run | ERR: Bad file descriptor (os error 8) | + | . | /sbin | ERR: Bad file descriptor (os error 8) | + | . | /srv | ERR: Bad file descriptor (os error 8) | + | . | /sys | ERR: Bad file descriptor (os error 8) | + | . | /tmp | ERR: Bad file descriptor (os error 8) | + | . | /usr | ERR: Bad file descriptor (os error 8) | + | . | /var | ERR: Bad file descriptor (os error 8) | | . | \0 | ERR: file name contained an unexpected NUL byte | - | . | /x/.. | ERR: Read-only file system (os error 69) | - | .. | | ERR: Read-only file system (os error 69) | - | .. | . | ERR: Read-only file system (os error 69) | - | .. | .. | ERR: Read-only file system (os error 69) | - | .. | / | ERR: Read-only file system (os error 69) | - | .. | /bin | ERR: Read-only file system (os error 69) | - | .. | /boot | ERR: Read-only file system (os error 69) | - | .. | /dev | ERR: Read-only file system (os error 69) | - | .. | /etc | ERR: Read-only file system (os error 69) | - | .. | /etc/group | ERR: Read-only file system (os error 69) | - | .. | /etc/passwd | ERR: Read-only file system (os error 69) | - | .. | /etc/shadow | ERR: Read-only file system (os error 69) | - | .. | /home | ERR: Read-only file system (os error 69) | - | .. | /lib | ERR: Read-only file system (os error 69) | - | .. | /lib64 | ERR: Read-only file system (os error 69) | - | .. | /opt | ERR: Read-only file system (os error 69) | - | .. | /proc | ERR: Read-only file system (os error 69) | - | .. | /proc/self | ERR: Read-only file system (os error 69) | - | .. | /root | ERR: Read-only file system (os error 69) | - | .. | /run | ERR: Read-only file system (os error 69) | - | .. | /sbin | ERR: Read-only file system (os error 69) | - | .. | /srv | ERR: Read-only file system (os error 69) | - | .. | /sys | ERR: Read-only file system (os error 69) | - | .. | /tmp | ERR: Read-only file system (os error 69) | - | .. | /usr | ERR: Read-only file system (os error 69) | - | .. | /var | ERR: Read-only file system (os error 69) | + | . | /x/.. | ERR: No such file or directory (os error 44) | + | .. | | ERR: Is a directory (os error 31) | + | .. | . | ERR: Is a directory (os error 31) | + | .. | .. | ERR: Is a directory (os error 31) | + | .. | / | ERR: Is a directory (os error 31) | + | .. | /bin | ERR: Bad file descriptor (os error 8) | + | .. | /boot | ERR: Bad file descriptor (os error 8) | + | .. | /dev | ERR: Bad file descriptor (os error 8) | + | .. | /etc | ERR: Bad file descriptor (os error 8) | + | .. | /etc/group | ERR: No such file or directory (os error 44) | + | .. | /etc/passwd | ERR: No such file or directory (os error 44) | + | .. | /etc/shadow | ERR: No such file or directory (os error 44) | + | .. | /home | ERR: Bad file descriptor (os error 8) | + | .. | /lib | ERR: Bad file descriptor (os error 8) | + | .. | /lib64 | ERR: Bad file descriptor (os error 8) | + | .. | /opt | ERR: Bad file descriptor (os error 8) | + | .. | /proc | ERR: Bad file descriptor (os error 8) | + | .. | /proc/self | ERR: No such file or directory (os error 44) | + | .. | /root | ERR: Bad file descriptor (os error 8) | + | .. | /run | ERR: Bad file descriptor (os error 8) | + | .. | /sbin | ERR: Bad file descriptor (os error 8) | + | .. | /srv | ERR: Bad file descriptor (os error 8) | + | .. | /sys | ERR: Bad file descriptor (os error 8) | + | .. | /tmp | ERR: Bad file descriptor (os error 8) | + | .. | /usr | ERR: Bad file descriptor (os error 8) | + | .. | /var | ERR: Bad file descriptor (os error 8) | | .. | \0 | ERR: file name contained an unexpected NUL byte | - | .. | /x/.. | ERR: Read-only file system (os error 69) | - | / | | ERR: Read-only file system (os error 69) | - | / | . | ERR: Read-only file system (os error 69) | - | / | .. | ERR: Read-only file system (os error 69) | - | / | / | ERR: Read-only file system (os error 69) | - | / | /bin | ERR: Read-only file system (os error 69) | - | / | /boot | ERR: Read-only file system (os error 69) | - | / | /dev | ERR: Read-only file system (os error 69) | - | / | /etc | ERR: Read-only file system (os error 69) | - | / | /etc/group | ERR: Read-only file system (os error 69) | - | / | /etc/passwd | ERR: Read-only file system (os error 69) | - | / | /etc/shadow | ERR: Read-only file system (os error 69) | - | / | /home | ERR: Read-only file system (os error 69) | - | / | /lib | ERR: Read-only file system (os error 69) | - | / | /lib64 | ERR: Read-only file system (os error 69) | - | / | /opt | ERR: Read-only file system (os error 69) | - | / | /proc | ERR: Read-only file system (os error 69) | - | / | /proc/self | ERR: Read-only file system (os error 69) | - | / | /root | ERR: Read-only file system (os error 69) | - | / | /run | ERR: Read-only file system (os error 69) | - | / | /sbin | ERR: Read-only file system (os error 69) | - | / | /srv | ERR: Read-only file system (os error 69) | - | / | /sys | ERR: Read-only file system (os error 69) | - | / | /tmp | ERR: Read-only file system (os error 69) | - | / | /usr | ERR: Read-only file system (os error 69) | - | / | /var | ERR: Read-only file system (os error 69) | + | .. | /x/.. | ERR: No such file or directory (os error 44) | + | / | | ERR: Is a directory (os error 31) | + | / | . | ERR: Is a directory (os error 31) | + | / | .. | ERR: Is a directory (os error 31) | + | / | / | ERR: Is a directory (os error 31) | + | / | /bin | ERR: Bad file descriptor (os error 8) | + | / | /boot | ERR: Bad file descriptor (os error 8) | + | / | /dev | ERR: Bad file descriptor (os error 8) | + | / | /etc | ERR: Bad file descriptor (os error 8) | + | / | /etc/group | ERR: No such file or directory (os error 44) | + | / | /etc/passwd | ERR: No such file or directory (os error 44) | + | / | /etc/shadow | ERR: No such file or directory (os error 44) | + | / | /home | ERR: Bad file descriptor (os error 8) | + | / | /lib | ERR: Bad file descriptor (os error 8) | + | / | /lib64 | ERR: Bad file descriptor (os error 8) | + | / | /opt | ERR: Bad file descriptor (os error 8) | + | / | /proc | ERR: Bad file descriptor (os error 8) | + | / | /proc/self | ERR: No such file or directory (os error 44) | + | / | /root | ERR: Bad file descriptor (os error 8) | + | / | /run | ERR: Bad file descriptor (os error 8) | + | / | /sbin | ERR: Bad file descriptor (os error 8) | + | / | /srv | ERR: Bad file descriptor (os error 8) | + | / | /sys | ERR: Bad file descriptor (os error 8) | + | / | /tmp | ERR: Bad file descriptor (os error 8) | + | / | /usr | ERR: Bad file descriptor (os error 8) | + | / | /var | ERR: Bad file descriptor (os error 8) | | / | \0 | ERR: file name contained an unexpected NUL byte | - | / | /x/.. | ERR: Read-only file system (os error 69) | + | / | /x/.. | ERR: No such file or directory (os error 44) | | /bin | | ERR: No such file or directory (os error 44) | | /bin | . | ERR: No such file or directory (os error 44) | | /bin | .. | ERR: No such file or directory (os error 44) | @@ -833,10 +833,10 @@ async fn test_create_dir() { +-------------+-------------------------------------------------+ | path | result | +-------------+-------------------------------------------------+ - | | ERR: Invalid argument (os error 28) | - | . | ERR: Invalid argument (os error 28) | - | .. | ERR: Invalid argument (os error 28) | - | / | ERR: Invalid argument (os error 28) | + | | OK: created | + | . | OK: created | + | .. | OK: created | + | / | OK: created | | /bin | OK: created | | /boot | OK: created | | /dev | OK: created | @@ -859,7 +859,7 @@ async fn test_create_dir() { | /usr | OK: created | | /var | OK: created | | \0 | ERR: file name contained an unexpected NUL byte | - | /x/.. | ERR: Invalid argument (os error 28) | + | /x/.. | ERR: No such file or directory (os error 44) | +-------------+-------------------------------------------------+ ", ); @@ -1695,33 +1695,33 @@ async fn test_open_append() { +-------------+-------------------------------------------------+ | path | result | +-------------+-------------------------------------------------+ - | | ERR: Read-only file system (os error 69) | - | . | ERR: Read-only file system (os error 69) | - | .. | ERR: Read-only file system (os error 69) | - | / | ERR: Read-only file system (os error 69) | - | /bin | ERR: Read-only file system (os error 69) | - | /boot | ERR: Read-only file system (os error 69) | - | /dev | ERR: Read-only file system (os error 69) | - | /etc | ERR: Read-only file system (os error 69) | - | /etc/group | ERR: Read-only file system (os error 69) | - | /etc/passwd | ERR: Read-only file system (os error 69) | - | /etc/shadow | ERR: Read-only file system (os error 69) | - | /home | ERR: Read-only file system (os error 69) | - | /lib | ERR: Read-only file system (os error 69) | - | /lib64 | ERR: Read-only file system (os error 69) | - | /opt | ERR: Read-only file system (os error 69) | - | /proc | ERR: Read-only file system (os error 69) | - | /proc/self | ERR: Read-only file system (os error 69) | - | /root | ERR: Read-only file system (os error 69) | - | /run | ERR: Read-only file system (os error 69) | - | /sbin | ERR: Read-only file system (os error 69) | - | /srv | ERR: Read-only file system (os error 69) | - | /sys | ERR: Read-only file system (os error 69) | - | /tmp | ERR: Read-only file system (os error 69) | - | /usr | ERR: Read-only file system (os error 69) | - | /var | ERR: Read-only file system (os error 69) | + | | OK: opened | + | . | OK: opened | + | .. | OK: opened | + | / | OK: opened | + | /bin | ERR: No such file or directory (os error 44) | + | /boot | ERR: No such file or directory (os error 44) | + | /dev | ERR: No such file or directory (os error 44) | + | /etc | ERR: No such file or directory (os error 44) | + | /etc/group | ERR: No such file or directory (os error 44) | + | /etc/passwd | ERR: No such file or directory (os error 44) | + | /etc/shadow | ERR: No such file or directory (os error 44) | + | /home | ERR: No such file or directory (os error 44) | + | /lib | ERR: No such file or directory (os error 44) | + | /lib64 | ERR: No such file or directory (os error 44) | + | /opt | ERR: No such file or directory (os error 44) | + | /proc | ERR: No such file or directory (os error 44) | + | /proc/self | ERR: No such file or directory (os error 44) | + | /root | ERR: No such file or directory (os error 44) | + | /run | ERR: No such file or directory (os error 44) | + | /sbin | ERR: No such file or directory (os error 44) | + | /srv | ERR: No such file or directory (os error 44) | + | /sys | ERR: No such file or directory (os error 44) | + | /tmp | ERR: No such file or directory (os error 44) | + | /usr | ERR: No such file or directory (os error 44) | + | /var | ERR: No such file or directory (os error 44) | | \0 | ERR: file name contained an unexpected NUL byte | - | /x/.. | ERR: Read-only file system (os error 69) | + | /x/.. | ERR: No such file or directory (os error 44) | +-------------+-------------------------------------------------+ ", ); @@ -1735,33 +1735,33 @@ async fn test_open_create() { +-------------+-------------------------------------------------+ | path | result | +-------------+-------------------------------------------------+ - | | ERR: Read-only file system (os error 69) | - | . | ERR: Read-only file system (os error 69) | - | .. | ERR: Read-only file system (os error 69) | - | / | ERR: Read-only file system (os error 69) | - | /bin | ERR: Read-only file system (os error 69) | - | /boot | ERR: Read-only file system (os error 69) | - | /dev | ERR: Read-only file system (os error 69) | - | /etc | ERR: Read-only file system (os error 69) | - | /etc/group | ERR: Read-only file system (os error 69) | - | /etc/passwd | ERR: Read-only file system (os error 69) | - | /etc/shadow | ERR: Read-only file system (os error 69) | - | /home | ERR: Read-only file system (os error 69) | - | /lib | ERR: Read-only file system (os error 69) | - | /lib64 | ERR: Read-only file system (os error 69) | - | /opt | ERR: Read-only file system (os error 69) | - | /proc | ERR: Read-only file system (os error 69) | - | /proc/self | ERR: Read-only file system (os error 69) | - | /root | ERR: Read-only file system (os error 69) | - | /run | ERR: Read-only file system (os error 69) | - | /sbin | ERR: Read-only file system (os error 69) | - | /srv | ERR: Read-only file system (os error 69) | - | /sys | ERR: Read-only file system (os error 69) | - | /tmp | ERR: Read-only file system (os error 69) | - | /usr | ERR: Read-only file system (os error 69) | - | /var | ERR: Read-only file system (os error 69) | + | | OK: opened | + | . | OK: opened | + | .. | OK: opened | + | / | OK: opened | + | /bin | ERR: No such file or directory (os error 44) | + | /boot | ERR: No such file or directory (os error 44) | + | /dev | ERR: No such file or directory (os error 44) | + | /etc | ERR: No such file or directory (os error 44) | + | /etc/group | ERR: No such file or directory (os error 44) | + | /etc/passwd | ERR: No such file or directory (os error 44) | + | /etc/shadow | ERR: No such file or directory (os error 44) | + | /home | ERR: No such file or directory (os error 44) | + | /lib | ERR: No such file or directory (os error 44) | + | /lib64 | ERR: No such file or directory (os error 44) | + | /opt | ERR: No such file or directory (os error 44) | + | /proc | ERR: No such file or directory (os error 44) | + | /proc/self | ERR: No such file or directory (os error 44) | + | /root | ERR: No such file or directory (os error 44) | + | /run | ERR: No such file or directory (os error 44) | + | /sbin | ERR: No such file or directory (os error 44) | + | /srv | ERR: No such file or directory (os error 44) | + | /sys | ERR: No such file or directory (os error 44) | + | /tmp | ERR: No such file or directory (os error 44) | + | /usr | ERR: No such file or directory (os error 44) | + | /var | ERR: No such file or directory (os error 44) | | \0 | ERR: file name contained an unexpected NUL byte | - | /x/.. | ERR: Read-only file system (os error 69) | + | /x/.. | ERR: No such file or directory (os error 44) | +-------------+-------------------------------------------------+ ", ); @@ -1775,33 +1775,33 @@ async fn test_open_create_new() { +-------------+-------------------------------------------------+ | path | result | +-------------+-------------------------------------------------+ - | | ERR: Read-only file system (os error 69) | - | . | ERR: Read-only file system (os error 69) | - | .. | ERR: Read-only file system (os error 69) | - | / | ERR: Read-only file system (os error 69) | - | /bin | ERR: Read-only file system (os error 69) | - | /boot | ERR: Read-only file system (os error 69) | - | /dev | ERR: Read-only file system (os error 69) | - | /etc | ERR: Read-only file system (os error 69) | - | /etc/group | ERR: Read-only file system (os error 69) | - | /etc/passwd | ERR: Read-only file system (os error 69) | - | /etc/shadow | ERR: Read-only file system (os error 69) | - | /home | ERR: Read-only file system (os error 69) | - | /lib | ERR: Read-only file system (os error 69) | - | /lib64 | ERR: Read-only file system (os error 69) | - | /opt | ERR: Read-only file system (os error 69) | - | /proc | ERR: Read-only file system (os error 69) | - | /proc/self | ERR: Read-only file system (os error 69) | - | /root | ERR: Read-only file system (os error 69) | - | /run | ERR: Read-only file system (os error 69) | - | /sbin | ERR: Read-only file system (os error 69) | - | /srv | ERR: Read-only file system (os error 69) | - | /sys | ERR: Read-only file system (os error 69) | - | /tmp | ERR: Read-only file system (os error 69) | - | /usr | ERR: Read-only file system (os error 69) | - | /var | ERR: Read-only file system (os error 69) | + | | ERR: File exists (os error 20) | + | . | ERR: File exists (os error 20) | + | .. | ERR: File exists (os error 20) | + | / | ERR: File exists (os error 20) | + | /bin | OK: opened | + | /boot | OK: opened | + | /dev | OK: opened | + | /etc | OK: opened | + | /etc/group | ERR: No such file or directory (os error 44) | + | /etc/passwd | ERR: No such file or directory (os error 44) | + | /etc/shadow | ERR: No such file or directory (os error 44) | + | /home | OK: opened | + | /lib | OK: opened | + | /lib64 | OK: opened | + | /opt | OK: opened | + | /proc | OK: opened | + | /proc/self | ERR: No such file or directory (os error 44) | + | /root | OK: opened | + | /run | OK: opened | + | /sbin | OK: opened | + | /srv | OK: opened | + | /sys | OK: opened | + | /tmp | OK: opened | + | /usr | OK: opened | + | /var | OK: opened | | \0 | ERR: file name contained an unexpected NUL byte | - | /x/.. | ERR: Read-only file system (os error 69) | + | /x/.. | ERR: No such file or directory (os error 44) | +-------------+-------------------------------------------------+ ", ); @@ -1855,33 +1855,33 @@ async fn test_open_truncate() { +-------------+-------------------------------------------------+ | path | result | +-------------+-------------------------------------------------+ - | | ERR: Read-only file system (os error 69) | - | . | ERR: Read-only file system (os error 69) | - | .. | ERR: Read-only file system (os error 69) | - | / | ERR: Read-only file system (os error 69) | - | /bin | ERR: Read-only file system (os error 69) | - | /boot | ERR: Read-only file system (os error 69) | - | /dev | ERR: Read-only file system (os error 69) | - | /etc | ERR: Read-only file system (os error 69) | - | /etc/group | ERR: Read-only file system (os error 69) | - | /etc/passwd | ERR: Read-only file system (os error 69) | - | /etc/shadow | ERR: Read-only file system (os error 69) | - | /home | ERR: Read-only file system (os error 69) | - | /lib | ERR: Read-only file system (os error 69) | - | /lib64 | ERR: Read-only file system (os error 69) | - | /opt | ERR: Read-only file system (os error 69) | - | /proc | ERR: Read-only file system (os error 69) | - | /proc/self | ERR: Read-only file system (os error 69) | - | /root | ERR: Read-only file system (os error 69) | - | /run | ERR: Read-only file system (os error 69) | - | /sbin | ERR: Read-only file system (os error 69) | - | /srv | ERR: Read-only file system (os error 69) | - | /sys | ERR: Read-only file system (os error 69) | - | /tmp | ERR: Read-only file system (os error 69) | - | /usr | ERR: Read-only file system (os error 69) | - | /var | ERR: Read-only file system (os error 69) | + | | ERR: Is a directory (os error 31) | + | . | ERR: Is a directory (os error 31) | + | .. | ERR: Is a directory (os error 31) | + | / | ERR: Is a directory (os error 31) | + | /bin | ERR: No such file or directory (os error 44) | + | /boot | ERR: No such file or directory (os error 44) | + | /dev | ERR: No such file or directory (os error 44) | + | /etc | ERR: No such file or directory (os error 44) | + | /etc/group | ERR: No such file or directory (os error 44) | + | /etc/passwd | ERR: No such file or directory (os error 44) | + | /etc/shadow | ERR: No such file or directory (os error 44) | + | /home | ERR: No such file or directory (os error 44) | + | /lib | ERR: No such file or directory (os error 44) | + | /lib64 | ERR: No such file or directory (os error 44) | + | /opt | ERR: No such file or directory (os error 44) | + | /proc | ERR: No such file or directory (os error 44) | + | /proc/self | ERR: No such file or directory (os error 44) | + | /root | ERR: No such file or directory (os error 44) | + | /run | ERR: No such file or directory (os error 44) | + | /sbin | ERR: No such file or directory (os error 44) | + | /srv | ERR: No such file or directory (os error 44) | + | /sys | ERR: No such file or directory (os error 44) | + | /tmp | ERR: No such file or directory (os error 44) | + | /usr | ERR: No such file or directory (os error 44) | + | /var | ERR: No such file or directory (os error 44) | | \0 | ERR: file name contained an unexpected NUL byte | - | /x/.. | ERR: Read-only file system (os error 69) | + | /x/.. | ERR: No such file or directory (os error 44) | +-------------+-------------------------------------------------+ ", ); @@ -1895,33 +1895,33 @@ async fn test_open_write() { +-------------+-------------------------------------------------+ | path | result | +-------------+-------------------------------------------------+ - | | ERR: Read-only file system (os error 69) | - | . | ERR: Read-only file system (os error 69) | - | .. | ERR: Read-only file system (os error 69) | - | / | ERR: Read-only file system (os error 69) | - | /bin | ERR: Read-only file system (os error 69) | - | /boot | ERR: Read-only file system (os error 69) | - | /dev | ERR: Read-only file system (os error 69) | - | /etc | ERR: Read-only file system (os error 69) | - | /etc/group | ERR: Read-only file system (os error 69) | - | /etc/passwd | ERR: Read-only file system (os error 69) | - | /etc/shadow | ERR: Read-only file system (os error 69) | - | /home | ERR: Read-only file system (os error 69) | - | /lib | ERR: Read-only file system (os error 69) | - | /lib64 | ERR: Read-only file system (os error 69) | - | /opt | ERR: Read-only file system (os error 69) | - | /proc | ERR: Read-only file system (os error 69) | - | /proc/self | ERR: Read-only file system (os error 69) | - | /root | ERR: Read-only file system (os error 69) | - | /run | ERR: Read-only file system (os error 69) | - | /sbin | ERR: Read-only file system (os error 69) | - | /srv | ERR: Read-only file system (os error 69) | - | /sys | ERR: Read-only file system (os error 69) | - | /tmp | ERR: Read-only file system (os error 69) | - | /usr | ERR: Read-only file system (os error 69) | - | /var | ERR: Read-only file system (os error 69) | + | | OK: opened | + | . | OK: opened | + | .. | OK: opened | + | / | OK: opened | + | /bin | ERR: No such file or directory (os error 44) | + | /boot | ERR: No such file or directory (os error 44) | + | /dev | ERR: No such file or directory (os error 44) | + | /etc | ERR: No such file or directory (os error 44) | + | /etc/group | ERR: No such file or directory (os error 44) | + | /etc/passwd | ERR: No such file or directory (os error 44) | + | /etc/shadow | ERR: No such file or directory (os error 44) | + | /home | ERR: No such file or directory (os error 44) | + | /lib | ERR: No such file or directory (os error 44) | + | /lib64 | ERR: No such file or directory (os error 44) | + | /opt | ERR: No such file or directory (os error 44) | + | /proc | ERR: No such file or directory (os error 44) | + | /proc/self | ERR: No such file or directory (os error 44) | + | /root | ERR: No such file or directory (os error 44) | + | /run | ERR: No such file or directory (os error 44) | + | /sbin | ERR: No such file or directory (os error 44) | + | /srv | ERR: No such file or directory (os error 44) | + | /sys | ERR: No such file or directory (os error 44) | + | /tmp | ERR: No such file or directory (os error 44) | + | /usr | ERR: No such file or directory (os error 44) | + | /var | ERR: No such file or directory (os error 44) | | \0 | ERR: file name contained an unexpected NUL byte | - | /x/.. | ERR: Read-only file system (os error 69) | + | /x/.. | ERR: No such file or directory (os error 44) | +-------------+-------------------------------------------------+ ", ); diff --git a/host/tests/integration_tests/python/runtime/fs.rs b/host/tests/integration_tests/python/runtime/fs.rs index 3ed40fd9..ebf5cf38 100644 --- a/host/tests/integration_tests/python/runtime/fs.rs +++ b/host/tests/integration_tests/python/runtime/fs.rs @@ -189,9 +189,10 @@ async fn test_write() { const CODE: &str = r#" def write(path: str) -> str: try: - open(path, "w") + open(path, "x") + return "OK" except Exception as e: - return f"ERR: {e}" + return f"{e}" raise Exception("unreachable") "#; @@ -205,15 +206,15 @@ def write(path: str) -> str: const CASES: &[TestCase] = &[ TestCase { path: "/", - err: "[Errno 69] Read-only file system: '/'", + err: "[Errno 20] File exists: '/'", }, TestCase { path: "/lib", - err: "[Errno 69] Read-only file system: '/lib'", + err: "[Errno 20] File exists: '/lib'", }, TestCase { path: "/test", - err: "[Errno 69] Read-only file system: '/test'", + err: "OK", }, ]; @@ -233,8 +234,7 @@ def write(path: str) -> str: assert_eq!( array.as_ref(), - &StringArray::from_iter(CASES.iter().map(|c| Some(format!("ERR: {}", c.err)))) - as &dyn Array, + &StringArray::from_iter(CASES.iter().map(|c| Some(c.err.to_string()))) as &dyn Array, ); }