From 5fa29628384f022bf1ef69d96df3d8e0c37f12fe Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Wed, 4 Feb 2026 20:26:23 +0000 Subject: [PATCH] [AtheOS] Implement xattrs operations. --- Aaru.Filesystems/AtheOS/Inode.cs | 101 +++++++++ Aaru.Filesystems/AtheOS/Unimplemented.cs | 7 - Aaru.Filesystems/AtheOS/Xattr.cs | 263 +++++++++++++++++++++++ 3 files changed, 364 insertions(+), 7 deletions(-) create mode 100644 Aaru.Filesystems/AtheOS/Xattr.cs diff --git a/Aaru.Filesystems/AtheOS/Inode.cs b/Aaru.Filesystems/AtheOS/Inode.cs index 16486c797..777bb13e0 100644 --- a/Aaru.Filesystems/AtheOS/Inode.cs +++ b/Aaru.Filesystems/AtheOS/Inode.cs @@ -27,6 +27,8 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; +using System.Linq; using Aaru.CommonTypes.Enums; using Aaru.Helpers; using Aaru.Logging; @@ -86,4 +88,103 @@ public sealed partial class AtheOS return ErrorNumber.NoError; } + + /// Reads an inode and returns both the parsed structure and raw data + /// Block address of the inode + /// Output parsed inode + /// Output raw inode data + /// Error code indicating success or failure + ErrorNumber ReadInodeWithData(long blockAddress, out Inode inode, out byte[] inodeData) + { + inode = default(Inode); + inodeData = null; + + uint sectorSize = _imagePlugin.Info.SectorSize; + long byteAddressInFS = blockAddress * _superblock.block_size; + long partitionByteOffset = (long)_partition.Start * sectorSize; + long absoluteByteAddress = byteAddressInFS + partitionByteOffset; + + long sectorAddress = absoluteByteAddress / sectorSize; + var offsetInSector = (int)(absoluteByteAddress % sectorSize); + int bytesToRead = _superblock.inode_size; + + int sectorsNeeded = (offsetInSector + bytesToRead + (int)sectorSize - 1) / (int)sectorSize; + + ErrorNumber errno = _imagePlugin.ReadSectors((ulong)sectorAddress, + false, + (uint)sectorsNeeded, + out byte[] sectorData, + out SectorStatus[] _); + + if(errno != ErrorNumber.NoError) return errno; + + inodeData = new byte[bytesToRead]; + Array.Copy(sectorData, offsetInSector, inodeData, 0, bytesToRead); + + inode = Marshal.ByteArrayToStructureLittleEndian(inodeData); + + return ErrorNumber.NoError; + } + + /// Gets the inode for a given path, returning both the parsed inode and raw data + /// Path to the file or directory + /// Output inode structure + /// Output raw inode data (for reading SmallData) + /// Error code indicating success or failure + ErrorNumber GetInodeForPath(string path, out Inode inode, out byte[] inodeData) + { + inode = default(Inode); + inodeData = null; + + // Normalize path + string normalizedPath = path ?? "/"; + + if(normalizedPath == "" || normalizedPath == ".") normalizedPath = "/"; + + // Root directory + if(normalizedPath == "/" || string.Equals(normalizedPath, "/", StringComparison.OrdinalIgnoreCase)) + { + long rootBlockAddress = (long)_superblock.root_dir_ag * _superblock.blocks_per_ag + + _superblock.root_dir_start; + + return ReadInodeWithData(rootBlockAddress, out inode, out inodeData); + } + + // Parse path components + string pathWithoutLeadingSlash = normalizedPath.StartsWith("/", StringComparison.Ordinal) + ? normalizedPath[1..] + : normalizedPath; + + string[] pathComponents = pathWithoutLeadingSlash.Split('/', StringSplitOptions.RemoveEmptyEntries) + .Where(static c => c != "." && c != "..") + .ToArray(); + + if(pathComponents.Length == 0) return ErrorNumber.InvalidArgument; + + // Traverse path + Dictionary currentEntries = _rootDirectoryCache; + + for(var i = 0; i < pathComponents.Length; i++) + { + string component = pathComponents[i]; + + if(!currentEntries.TryGetValue(component, out long childInodeAddr)) return ErrorNumber.NoSuchFile; + + // Last component - return its inode + if(i == pathComponents.Length - 1) return ReadInodeWithData(childInodeAddr, out inode, out inodeData); + + // Not last component - must be a directory + ErrorNumber errno = ReadInode(childInodeAddr, out Inode childInode); + + if(errno != ErrorNumber.NoError) return errno; + + if(!IsDirectory(childInode)) return ErrorNumber.NotDirectory; + + errno = ParseDirectoryBTree(childInode.data, out currentEntries); + + if(errno != ErrorNumber.NoError) return errno; + } + + return ErrorNumber.NoSuchFile; + } } \ No newline at end of file diff --git a/Aaru.Filesystems/AtheOS/Unimplemented.cs b/Aaru.Filesystems/AtheOS/Unimplemented.cs index b3f11f646..de43f56b2 100644 --- a/Aaru.Filesystems/AtheOS/Unimplemented.cs +++ b/Aaru.Filesystems/AtheOS/Unimplemented.cs @@ -27,7 +27,6 @@ // ****************************************************************************/ using System; -using System.Collections.Generic; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; @@ -37,12 +36,6 @@ namespace Aaru.Filesystems; /// public sealed partial class AtheOS { - /// - public ErrorNumber ListXAttr(string path, out List xattrs) => throw new NotImplementedException(); - - /// - public ErrorNumber GetXattr(string path, string xattr, ref byte[] buf) => throw new NotImplementedException(); - /// public ErrorNumber StatFs(out FileSystemInfo stat) => throw new NotImplementedException(); diff --git a/Aaru.Filesystems/AtheOS/Xattr.cs b/Aaru.Filesystems/AtheOS/Xattr.cs new file mode 100644 index 000000000..f9a2204ed --- /dev/null +++ b/Aaru.Filesystems/AtheOS/Xattr.cs @@ -0,0 +1,263 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Xattr.cs +// Author(s) : Natalia Portillo +// +// Component : Atheos filesystem plugin. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using Aaru.CommonTypes.Enums; +using Aaru.Helpers; +using Aaru.Logging; + +namespace Aaru.Filesystems; + +/// +public sealed partial class AtheOS +{ + /// + public ErrorNumber ListXAttr(string path, out List xattrs) + { + xattrs = null; + + if(!_mounted) return ErrorNumber.AccessDenied; + + AaruLogging.Debug(MODULE_NAME, "ListXAttr: path='{0}'", path); + + // Get the inode for the path + ErrorNumber errno = GetInodeForPath(path, out Inode inode, out byte[] inodeData); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error getting inode for path: {0}", errno); + + return errno; + } + + xattrs = new List(); + + // First, read SmallData attributes from the inode itself + // SmallData starts after the Inode structure within the inode block + int smallDataOffset = _superblock.inode_size > 0 ? Marshal.SizeOf() : 0; + + int smallDataSize = _superblock.inode_size - smallDataOffset; + + if(smallDataSize > 0 && inodeData != null && inodeData.Length >= _superblock.inode_size) + { + int offset = smallDataOffset; + + while(offset + 8 <= _superblock.inode_size) // SmallData header is 8 bytes + { + // Read SmallData header + var sdType = BitConverter.ToUInt32(inodeData, offset); + var sdNameSize = BitConverter.ToUInt16(inodeData, offset + 4); + var sdDataSize = BitConverter.ToUInt16(inodeData, offset + 6); + + // End of small data entries + if(sdNameSize == 0) break; + + // Validate bounds + if(offset + 8 + sdNameSize + sdDataSize > _superblock.inode_size) break; + + // Extract attribute name + string attrName = _encoding.GetString(inodeData, offset + 8, sdNameSize); + xattrs.Add(attrName); + + AaruLogging.Debug(MODULE_NAME, + "Found SmallData attribute: name='{0}', type={1}, dataSize={2}", + attrName, + sdType, + sdDataSize); + + // Move to next entry + offset += 8 + sdNameSize + sdDataSize; + } + } + + // Then, read attributes from attribute directory if it exists + if(inode.attrib_dir.len > 0) + { + long attrDirBlockAddr = (long)inode.attrib_dir.group * _superblock.blocks_per_ag + inode.attrib_dir.start; + + AaruLogging.Debug(MODULE_NAME, "Reading attribute directory at block {0}", attrDirBlockAddr); + + errno = ReadInode(attrDirBlockAddr, out Inode attrDirInode); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading attribute directory inode: {0}", errno); + + // Return what we have from SmallData + return ErrorNumber.NoError; + } + + // Parse the attribute directory's B+tree to get attribute names + errno = ParseDirectoryBTree(attrDirInode.data, out Dictionary attrEntries); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error parsing attribute directory B+tree: {0}", errno); + + // Return what we have from SmallData + return ErrorNumber.NoError; + } + + // Add attribute names from the directory + foreach(string attrName in attrEntries.Keys) + { + if(!xattrs.Contains(attrName)) xattrs.Add(attrName); + } + } + + AaruLogging.Debug(MODULE_NAME, "ListXAttr: found {0} attributes", xattrs.Count); + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber GetXattr(string path, string xattr, ref byte[] buf) + { + if(!_mounted) return ErrorNumber.AccessDenied; + + AaruLogging.Debug(MODULE_NAME, "GetXattr: path='{0}', xattr='{1}'", path, xattr); + + // Get the inode for the path + ErrorNumber errno = GetInodeForPath(path, out Inode inode, out byte[] inodeData); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error getting inode for path: {0}", errno); + + return errno; + } + + // First, try to find in SmallData + int smallDataOffset = Marshal.SizeOf(); + int smallDataSize = _superblock.inode_size - smallDataOffset; + + if(smallDataSize > 0 && inodeData != null && inodeData.Length >= _superblock.inode_size) + { + int offset = smallDataOffset; + + while(offset + 8 <= _superblock.inode_size) + { + var sdNameSize = BitConverter.ToUInt16(inodeData, offset + 4); + var sdDataSize = BitConverter.ToUInt16(inodeData, offset + 6); + + if(sdNameSize == 0) break; + + if(offset + 8 + sdNameSize + sdDataSize > _superblock.inode_size) break; + + string attrName = _encoding.GetString(inodeData, offset + 8, sdNameSize); + + if(attrName == xattr) + { + // Found the attribute in SmallData + buf = new byte[sdDataSize]; + Array.Copy(inodeData, offset + 8 + sdNameSize, buf, 0, sdDataSize); + + AaruLogging.Debug(MODULE_NAME, + "GetXattr: found SmallData attribute '{0}', size={1}", + xattr, + sdDataSize); + + return ErrorNumber.NoError; + } + + offset += 8 + sdNameSize + sdDataSize; + } + } + + // Not found in SmallData, try attribute directory + if(inode.attrib_dir.len == 0) + { + AaruLogging.Debug(MODULE_NAME, "Attribute '{0}' not found (no attribute directory)", xattr); + + return ErrorNumber.NoSuchExtendedAttribute; + } + + long attrDirBlockAddr = (long)inode.attrib_dir.group * _superblock.blocks_per_ag + inode.attrib_dir.start; + + errno = ReadInode(attrDirBlockAddr, out Inode attrDirInode); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading attribute directory inode: {0}", errno); + + return errno; + } + + // Parse the attribute directory to find the attribute + errno = ParseDirectoryBTree(attrDirInode.data, out Dictionary attrEntries); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error parsing attribute directory B+tree: {0}", errno); + + return errno; + } + + if(!attrEntries.TryGetValue(xattr, out long attrInodeAddr)) + { + AaruLogging.Debug(MODULE_NAME, "Attribute '{0}' not found in attribute directory", xattr); + + return ErrorNumber.NoSuchExtendedAttribute; + } + + // Read the attribute inode + errno = ReadInode(attrInodeAddr, out Inode attrInode); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading attribute inode: {0}", errno); + + return errno; + } + + // Read the attribute data from the attribute inode's data stream + if(attrInode.data.size <= 0) + { + buf = []; + + return ErrorNumber.NoError; + } + + errno = ReadFromDataStream(attrInode.data, 0, (int)attrInode.data.size, out buf); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading attribute data: {0}", errno); + + return errno; + } + + AaruLogging.Debug(MODULE_NAME, + "GetXattr: found attribute '{0}' in attribute directory, size={1}", + xattr, + buf.Length); + + return ErrorNumber.NoError; + } +} \ No newline at end of file