mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
[F2FS] Implement directory operations.
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Logging;
|
||||
@@ -36,8 +37,43 @@ namespace Aaru.Filesystems;
|
||||
|
||||
public sealed partial class F2FS
|
||||
{
|
||||
/// <summary>Reads the directory entries for a given inode into a dictionary</summary>
|
||||
/// <param name="nid">Node ID of the directory inode</param>
|
||||
/// <param name="entries">Output dictionary mapping filename → inode number</param>
|
||||
/// <returns>Error code indicating success or failure</returns>
|
||||
ErrorNumber ReadDirectoryEntries(uint nid, out Dictionary<string, uint> entries)
|
||||
{
|
||||
entries = new Dictionary<string, uint>();
|
||||
|
||||
// Resolve inode block address via NAT
|
||||
ErrorNumber errno = LookupNat(nid, out uint blockAddr);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(blockAddr == 0) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Read the inode node block
|
||||
errno = ReadBlock(blockAddr, out byte[] nodeBlock);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Parse the inode
|
||||
Inode inode = Marshal.ByteArrayToStructureLittleEndian<Inode>(nodeBlock);
|
||||
|
||||
// Validate it's a directory
|
||||
if((inode.i_mode & 0xF000) != 0x4000) return ErrorNumber.NotDirectory;
|
||||
|
||||
// Check if this directory uses inline dentry
|
||||
if((inode.i_inline & F2FS_INLINE_DENTRY) != 0)
|
||||
ParseInlineDentry(inode, nodeBlock, entries);
|
||||
else
|
||||
errno = ParseRegularDentry(inode, entries);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
/// <summary>Parses inline dentry from the inode's node block</summary>
|
||||
void ParseInlineDentry(Inode inode, byte[] nodeBlock)
|
||||
void ParseInlineDentry(Inode inode, byte[] nodeBlock, Dictionary<string, uint> entries)
|
||||
{
|
||||
// For inline dentry, the data is stored in the inode's i_addr area
|
||||
// The inline data starts after the extra attribute area (if present) + reserved word
|
||||
@@ -98,16 +134,16 @@ public sealed partial class F2FS
|
||||
int dentryStart = reservedEnd;
|
||||
int filenameStart = dentryStart + nrInlineDentry * 11;
|
||||
|
||||
ParseDentryBitmapEntries(nodeBlock, bitmapStart, dentryStart, filenameStart, nrInlineDentry);
|
||||
ParseDentryBitmapEntries(nodeBlock, bitmapStart, dentryStart, filenameStart, nrInlineDentry, entries);
|
||||
}
|
||||
|
||||
/// <summary>Parses regular (non-inline) dentry blocks from the inode's data blocks</summary>
|
||||
ErrorNumber ParseRegularDentry(Inode inode)
|
||||
ErrorNumber ParseRegularDentry(Inode inode, Dictionary<string, uint> entries)
|
||||
{
|
||||
// Number of data blocks = ceil(i_size / blockSize)
|
||||
ulong nPages = (inode.i_size + _blockSize - 1) / _blockSize;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Reading {0} dentry blocks from root inode", nPages);
|
||||
AaruLogging.Debug(MODULE_NAME, "Reading {0} dentry blocks", nPages);
|
||||
|
||||
// Get the extra isize in uint32 count
|
||||
var extraIsize = 0;
|
||||
@@ -158,7 +194,8 @@ public sealed partial class F2FS
|
||||
0,
|
||||
SIZE_OF_DENTRY_BITMAP + SIZE_OF_RESERVED,
|
||||
SIZE_OF_DENTRY_BITMAP + SIZE_OF_RESERVED + NR_DENTRY_IN_BLOCK * 11,
|
||||
NR_DENTRY_IN_BLOCK);
|
||||
NR_DENTRY_IN_BLOCK,
|
||||
entries);
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
@@ -167,7 +204,8 @@ public sealed partial class F2FS
|
||||
/// <summary>
|
||||
/// Parses dentry entries from a bitmap/dentry/filename layout (used by both inline and regular dentry blocks)
|
||||
/// </summary>
|
||||
void ParseDentryBitmapEntries(byte[] data, int bitmapOffset, int dentryOffset, int filenameOffset, int maxDentries)
|
||||
static void ParseDentryBitmapEntries(byte[] data, int bitmapOffset, int dentryOffset, int filenameOffset,
|
||||
int maxDentries, Dictionary<string, uint> entries)
|
||||
{
|
||||
var bitPos = 0;
|
||||
|
||||
@@ -205,10 +243,8 @@ public sealed partial class F2FS
|
||||
|
||||
string fileName = Encoding.UTF8.GetString(data, fnOffset, nameLen);
|
||||
|
||||
if(!string.IsNullOrEmpty(fileName) &&
|
||||
fileName is not ("." or "..") &&
|
||||
!_rootDirectoryCache.ContainsKey(fileName))
|
||||
_rootDirectoryCache[fileName] = ino;
|
||||
if(!string.IsNullOrEmpty(fileName) && fileName is not ("." or "..") && !entries.ContainsKey(fileName))
|
||||
entries[fileName] = ino;
|
||||
|
||||
// Advance past the slots used by this entry
|
||||
int slots = (nameLen + 8 - 1) / 8; // GET_DENTRY_SLOTS
|
||||
|
||||
166
Aaru.Filesystems/F2FS/Dir.cs
Normal file
166
Aaru.Filesystems/F2FS/Dir.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Dir.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : F2FS 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
public sealed partial class F2FS
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber OpenDir(string path, out IDirNode node)
|
||||
{
|
||||
node = null;
|
||||
|
||||
if(!_mounted) return ErrorNumber.AccessDenied;
|
||||
|
||||
// Normalize the path
|
||||
string normalizedPath = path ?? "/";
|
||||
|
||||
if(normalizedPath is "" or ".") normalizedPath = "/";
|
||||
|
||||
// Root directory — return cached entries
|
||||
if(normalizedPath == "/")
|
||||
{
|
||||
if(_rootDirectoryCache.Count == 0) return ErrorNumber.NoSuchFile;
|
||||
|
||||
node = new F2fsDirNode
|
||||
{
|
||||
Path = "/",
|
||||
Position = 0,
|
||||
Entries = _rootDirectoryCache.Keys.ToArray()
|
||||
};
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Subdirectory traversal
|
||||
string pathWithoutLeadingSlash = normalizedPath.StartsWith("/", StringComparison.Ordinal)
|
||||
? normalizedPath[1..]
|
||||
: normalizedPath;
|
||||
|
||||
string[] pathComponents = pathWithoutLeadingSlash.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(pathComponents.Length == 0) return ErrorNumber.InvalidArgument;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenDir: Traversing path with {0} components", pathComponents.Length);
|
||||
|
||||
// Start from root directory cache
|
||||
Dictionary<string, uint> currentEntries = _rootDirectoryCache;
|
||||
|
||||
// Traverse each path component
|
||||
for(var p = 0; p < pathComponents.Length; p++)
|
||||
{
|
||||
string component = pathComponents[p];
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenDir: Navigating to component '{0}'", component);
|
||||
|
||||
// Skip "." and ".." during traversal
|
||||
if(component is "." or "..") continue;
|
||||
|
||||
// Find the component in current directory
|
||||
if(!currentEntries.TryGetValue(component, out uint inodeNumber))
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenDir: Component '{0}' not found in directory", component);
|
||||
|
||||
return ErrorNumber.NoSuchFile;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenDir: Component '{0}' found with inode {1}", component, inodeNumber);
|
||||
|
||||
// Read directory contents for this inode
|
||||
ErrorNumber errno = ReadDirectoryEntries(inodeNumber, out Dictionary<string, uint> childEntries);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenDir: Error reading directory for '{0}': {1}", component, errno);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
// If this is the last component, create the directory node
|
||||
if(p == pathComponents.Length - 1)
|
||||
{
|
||||
node = new F2fsDirNode
|
||||
{
|
||||
Path = normalizedPath,
|
||||
Position = 0,
|
||||
Entries = childEntries.Keys.ToArray()
|
||||
};
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"OpenDir: Opened directory '{0}' with {1} entries",
|
||||
normalizedPath,
|
||||
childEntries.Count);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Not the last component — continue traversing
|
||||
currentEntries = childEntries;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoSuchFile;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber CloseDir(IDirNode node)
|
||||
{
|
||||
if(node is not F2fsDirNode f2fsNode) return ErrorNumber.InvalidArgument;
|
||||
|
||||
f2fsNode.Position = -1;
|
||||
f2fsNode.Entries = null;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadDir(IDirNode node, out string filename)
|
||||
{
|
||||
filename = null;
|
||||
|
||||
if(!_mounted) return ErrorNumber.AccessDenied;
|
||||
|
||||
if(node is not F2fsDirNode f2fsNode) return ErrorNumber.InvalidArgument;
|
||||
|
||||
if(f2fsNode.Position < 0) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// End of directory
|
||||
if(f2fsNode.Position >= f2fsNode.Entries.Length) return ErrorNumber.NoError;
|
||||
|
||||
// Get current filename and advance position
|
||||
filename = f2fsNode.Entries[f2fsNode.Position++];
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
47
Aaru.Filesystems/F2FS/Internal.cs
Normal file
47
Aaru.Filesystems/F2FS/Internal.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Internal.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : F2FS 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
public sealed partial class F2FS
|
||||
{
|
||||
/// <summary>Directory node for enumerating directory contents</summary>
|
||||
sealed class F2fsDirNode : IDirNode
|
||||
{
|
||||
/// <summary>Current position in the directory enumeration (entry index)</summary>
|
||||
internal int Position { get; set; }
|
||||
|
||||
/// <summary>Array of directory entry names in this directory</summary>
|
||||
internal string[] Entries { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Path { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -211,68 +211,16 @@ public sealed partial class F2FS
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Loading root directory, root_ino={0}", _superblock.root_ino);
|
||||
|
||||
// Resolve root inode block address via NAT
|
||||
ErrorNumber errno = LookupNat(_superblock.root_ino, out uint rootBlockAddr);
|
||||
ErrorNumber errno = ReadDirectoryEntries(_superblock.root_ino, out Dictionary<string, uint> entries);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Error looking up root inode in NAT: {0}", errno);
|
||||
AaruLogging.Debug(MODULE_NAME, "Error reading root directory entries: {0}", errno);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Root inode block address: {0}", rootBlockAddr);
|
||||
|
||||
if(rootBlockAddr == 0)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Root inode block address is NULL_ADDR");
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Read the root inode node block
|
||||
errno = ReadBlock(rootBlockAddr, out byte[] nodeBlock);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Error reading root inode block: {0}", errno);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
// Parse the inode from the node block
|
||||
Inode rootInode = Marshal.ByteArrayToStructureLittleEndian<Inode>(nodeBlock);
|
||||
|
||||
// Validate it's a directory
|
||||
// S_IFDIR = 0x4000 in POSIX mode bits
|
||||
if((rootInode.i_mode & 0xF000) != 0x4000)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Root inode is not a directory (mode: 0x{0:X4})", rootInode.i_mode);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Root inode valid: mode=0x{0:X4}, size={1}, inline=0x{2:X2}",
|
||||
rootInode.i_mode,
|
||||
rootInode.i_size,
|
||||
rootInode.i_inline);
|
||||
|
||||
// Check if this directory uses inline dentry
|
||||
if((rootInode.i_inline & F2FS_INLINE_DENTRY) != 0)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Root directory uses inline dentry");
|
||||
|
||||
ParseInlineDentry(rootInode, nodeBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Root directory uses regular dentry blocks");
|
||||
|
||||
errno = ParseRegularDentry(rootInode);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
foreach(KeyValuePair<string, uint> kvp in entries) _rootDirectoryCache[kvp.Key] = kvp.Value;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Root directory loaded, {0} entries cached", _rootDirectoryCache.Count);
|
||||
|
||||
|
||||
@@ -60,13 +60,4 @@ public sealed partial class F2FS
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber OpenDir(string path, out IDirNode node) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber CloseDir(IDirNode node) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadDir(IDirNode node, out string filename) => throw new NotImplementedException();
|
||||
}
|
||||
Reference in New Issue
Block a user