diff --git a/Aaru.Filesystems/VxFS/File.cs b/Aaru.Filesystems/VxFS/File.cs
index 0a9dd5c33..7324fa53f 100644
--- a/Aaru.Filesystems/VxFS/File.cs
+++ b/Aaru.Filesystems/VxFS/File.cs
@@ -29,6 +29,7 @@
using System;
using System.Collections.Generic;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Helpers;
@@ -54,6 +55,100 @@ public sealed partial class VxFS
return ErrorNumber.NoError;
}
+ ///
+ public ErrorNumber OpenFile(string path, out IFileNode node)
+ {
+ node = null;
+
+ if(!_mounted) return ErrorNumber.AccessDenied;
+
+ if(string.IsNullOrEmpty(path) || path == "/") return ErrorNumber.IsDirectory;
+
+ ErrorNumber errno = LookupInode(path, out DiskInode inode, out uint inodeNumber);
+
+ if(errno != ErrorNumber.NoError) return errno;
+
+ var fileType = (VxfsFileType)(inode.vdi_mode & VXFS_TYPE_MASK);
+
+ if(fileType == VxfsFileType.Dir) return ErrorNumber.IsDirectory;
+
+ node = new VxFsFileNode
+ {
+ Path = path,
+ Length = (long)inode.vdi_size,
+ Offset = 0,
+ InodeNumber = inodeNumber,
+ Inode = inode
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ ///
+ public ErrorNumber CloseFile(IFileNode node)
+ {
+ if(!_mounted) return ErrorNumber.AccessDenied;
+
+ if(node is not VxFsFileNode) return ErrorNumber.InvalidArgument;
+
+ return ErrorNumber.NoError;
+ }
+
+ ///
+ public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read)
+ {
+ read = 0;
+
+ if(!_mounted) return ErrorNumber.AccessDenied;
+
+ if(node is not VxFsFileNode fileNode) return ErrorNumber.InvalidArgument;
+
+ if(buffer == null) return ErrorNumber.InvalidArgument;
+
+ if(fileNode.Offset < 0 || fileNode.Offset >= fileNode.Length) return ErrorNumber.InvalidArgument;
+
+ long toRead = length;
+
+ if(fileNode.Offset + toRead > fileNode.Length) toRead = fileNode.Length - fileNode.Offset;
+
+ if(toRead <= 0) return ErrorNumber.NoError;
+
+ if(toRead > buffer.Length) toRead = buffer.Length;
+
+ int blockSize = _superblock.vs_bsize;
+ long bytesRead = 0;
+ long currentOffset = fileNode.Offset;
+
+ while(bytesRead < toRead)
+ {
+ var blockNum = (uint)(currentOffset / blockSize);
+ var offsetInBlock = (int)(currentOffset % blockSize);
+
+ byte[] blockData = ReadInodeBlock(fileNode.Inode, blockNum);
+
+ if(blockData == null)
+ {
+ // Sparse block / hole — fill with zeros
+ long bytesToZero = Math.Min(blockSize - offsetInBlock, toRead - bytesRead);
+ Array.Clear(buffer, (int)bytesRead, (int)bytesToZero);
+ bytesRead += bytesToZero;
+ currentOffset += bytesToZero;
+
+ continue;
+ }
+
+ long bytesToCopy = Math.Min(blockSize - offsetInBlock, toRead - bytesRead);
+ Array.Copy(blockData, offsetInBlock, buffer, bytesRead, bytesToCopy);
+ bytesRead += bytesToCopy;
+ currentOffset += bytesToCopy;
+ }
+
+ read = bytesRead;
+ fileNode.Offset += bytesRead;
+
+ return ErrorNumber.NoError;
+ }
+
/// Resolves a path to its inode
/// Path to resolve
/// Output inode
diff --git a/Aaru.Filesystems/VxFS/Internal.cs b/Aaru.Filesystems/VxFS/Internal.cs
index 385f46208..c005ef47e 100644
--- a/Aaru.Filesystems/VxFS/Internal.cs
+++ b/Aaru.Filesystems/VxFS/Internal.cs
@@ -46,4 +46,23 @@ public sealed partial class VxFS
///
public string Path { get; init; }
}
+
+ /// File node for reading file contents
+ sealed class VxFsFileNode : IFileNode
+ {
+ /// The inode number
+ internal uint InodeNumber { get; init; }
+
+ /// The file's disk inode containing metadata and extent pointers
+ internal DiskInode Inode { get; init; }
+
+ ///
+ public string Path { get; init; }
+
+ ///
+ public long Length { get; init; }
+
+ ///
+ public long Offset { get; set; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/VxFS/Unimplemented.cs b/Aaru.Filesystems/VxFS/Unimplemented.cs
index 3a0081547..843e3bf51 100644
--- a/Aaru.Filesystems/VxFS/Unimplemented.cs
+++ b/Aaru.Filesystems/VxFS/Unimplemented.cs
@@ -28,7 +28,6 @@
using System;
using Aaru.CommonTypes.Enums;
-using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
namespace Aaru.Filesystems;
@@ -40,14 +39,4 @@ public sealed partial class VxFS
///
public ErrorNumber ReadLink(string path, out string dest) => throw new NotImplementedException();
-
- ///
- public ErrorNumber OpenFile(string path, out IFileNode node) => throw new NotImplementedException();
-
- ///
- public ErrorNumber CloseFile(IFileNode node) => throw new NotImplementedException();
-
- ///
- public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) =>
- throw new NotImplementedException();
}
\ No newline at end of file