[cramfs] Fix handling big-endian inodes.

This commit is contained in:
2026-02-08 15:09:21 +00:00
parent 766b466dfa
commit 1465e29a3d
7 changed files with 131 additions and 63 deletions

View File

@@ -102,19 +102,19 @@ public sealed partial class Cram
AaruLogging.Debug(MODULE_NAME, "OpenDir: Component '{0}' found", component);
// Check if it's a directory
if(!IsDirectory(entry.Inode.Mode))
if(!IsDirectory(GetInodeMode(entry.Inode)))
{
AaruLogging.Debug(MODULE_NAME,
"OpenDir: '{0}' is not a directory (mode=0x{1:X4})",
component,
entry.Inode.Mode);
GetInodeMode(entry.Inode));
return ErrorNumber.NotDirectory;
}
// Read directory contents
uint dirOffset = entry.Inode.Offset << 2;
uint dirSize = entry.Inode.Size;
uint dirOffset = GetInodeOffset(entry.Inode) << 2;
uint dirSize = GetInodeSize(entry.Inode);
var dirEntries = new Dictionary<string, DirectoryEntryInfo>(StringComparer.Ordinal);
@@ -220,7 +220,7 @@ public sealed partial class Cram
: Marshal.ByteArrayToStructureBigEndian<Inode>(inodeData);
// Name length is stored as (actual_length + 3) / 4, so multiply by 4 to get padded length
int nameLen = inode.NameLen << 2;
int nameLen = GetInodeNameLen(inode) << 2;
if(nameLen == 0)
{

View File

@@ -98,7 +98,7 @@ public sealed partial class Cram
}
// Not the last component - must be a directory
if(!IsDirectory(entry.Inode.Mode))
if(!IsDirectory(GetInodeMode(entry.Inode)))
{
AaruLogging.Debug(MODULE_NAME, "Stat: '{0}' is not a directory", component);
@@ -106,8 +106,8 @@ public sealed partial class Cram
}
// Read directory contents for next iteration
uint dirOffset = entry.Inode.Offset << 2;
uint dirSize = entry.Inode.Size;
uint dirOffset = GetInodeOffset(entry.Inode) << 2;
uint dirSize = GetInodeSize(entry.Inode);
var dirEntries = new Dictionary<string, DirectoryEntryInfo>(StringComparer.Ordinal);
@@ -165,7 +165,7 @@ public sealed partial class Cram
return errno;
}
uint size = entry.Inode.Size;
uint size = GetInodeSize(entry.Inode);
uint blockCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
node = new CramFileNode
@@ -174,7 +174,7 @@ public sealed partial class Cram
Length = size,
Offset = 0,
Inode = entry.Inode,
BlockPtrOffset = entry.Inode.Offset << 2,
BlockPtrOffset = GetInodeOffset(entry.Inode) << 2,
BlockCount = blockCount
};
@@ -219,7 +219,7 @@ public sealed partial class Cram
if(errno != ErrorNumber.NoError) return errno;
uint size = entry.Inode.Size;
uint size = GetInodeSize(entry.Inode);
if(size == 0)
{
@@ -238,7 +238,7 @@ public sealed partial class Cram
Length = size,
Offset = 0,
Inode = entry.Inode,
BlockPtrOffset = entry.Inode.Offset << 2,
BlockPtrOffset = GetInodeOffset(entry.Inode) << 2,
BlockCount = blockCount
};
@@ -381,11 +381,11 @@ public sealed partial class Cram
}
// Not the last component - must be a directory
if(!IsDirectory(foundEntry.Inode.Mode)) return ErrorNumber.NotDirectory;
if(!IsDirectory(GetInodeMode(foundEntry.Inode))) return ErrorNumber.NotDirectory;
// Read directory contents for next iteration
uint dirOffset = foundEntry.Inode.Offset << 2;
uint dirSize = foundEntry.Inode.Size;
uint dirOffset = GetInodeOffset(foundEntry.Inode) << 2;
uint dirSize = GetInodeSize(foundEntry.Inode);
var dirEntries = new Dictionary<string, DirectoryEntryInfo>(StringComparer.Ordinal);

View File

@@ -0,0 +1,77 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Helpers.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Cram file system 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
// ****************************************************************************/
namespace Aaru.Filesystems;
public sealed partial class Cram
{
/// <summary>Gets the file mode from an inode based on current endianness</summary>
/// <remarks>
/// Little-endian layout: mode in bits 0-15 of modeUid
/// Big-endian layout: mode in bits 16-31 of modeUid
/// </remarks>
ushort GetInodeMode(Inode inode) =>
_littleEndian ? (ushort)(inode.modeUid & 0xFFFF) : (ushort)(inode.modeUid >> 16);
/// <summary>Gets the user ID from an inode based on current endianness</summary>
/// <remarks>
/// Little-endian layout: uid in bits 16-31 of modeUid
/// Big-endian layout: uid in bits 0-15 of modeUid
/// </remarks>
ushort GetInodeUid(Inode inode) => _littleEndian ? (ushort)(inode.modeUid >> 16) : (ushort)(inode.modeUid & 0xFFFF);
/// <summary>Gets the file size from an inode based on current endianness</summary>
/// <remarks>
/// Little-endian layout: size in bits 0-23 of sizeGid
/// Big-endian layout: size in bits 8-31 of sizeGid
/// </remarks>
uint GetInodeSize(Inode inode) => _littleEndian ? inode.sizeGid & 0xFFFFFF : inode.sizeGid >> 8;
/// <summary>Gets the group ID from an inode based on current endianness</summary>
/// <remarks>
/// Little-endian layout: gid in bits 24-31 of sizeGid
/// Big-endian layout: gid in bits 0-7 of sizeGid
/// </remarks>
byte GetInodeGid(Inode inode) => _littleEndian ? (byte)(inode.sizeGid >> 24) : (byte)(inode.sizeGid & 0xFF);
/// <summary>Gets the name length from an inode based on current endianness</summary>
/// <remarks>
/// Little-endian layout: namelen in bits 0-5 of namelenOffset
/// Big-endian layout: namelen in bits 26-31 of namelenOffset
/// </remarks>
byte GetInodeNameLen(Inode inode) => _littleEndian
? (byte)(inode.namelenOffset & 0x3F)
: (byte)(inode.namelenOffset >> 26);
/// <summary>Gets the data offset from an inode based on current endianness</summary>
/// <remarks>
/// Little-endian layout: offset in bits 6-31 of namelenOffset
/// Big-endian layout: offset in bits 0-25 of namelenOffset
/// </remarks>
uint GetInodeOffset(Inode inode) => _littleEndian ? inode.namelenOffset >> 6 : inode.namelenOffset & 0x3FFFFFF;
}

View File

@@ -124,10 +124,14 @@ public sealed partial class Cram
}
// Display root inode information
sbInformation.AppendFormat(Localization.Cram_Root_directory_mode_0, Convert.ToString(crSb.root.Mode, 8))
.AppendLine();
// Bit extraction depends on endianness - LE: mode in low bits, BE: mode in high bits
ushort rootMode = littleEndian ? (ushort)(crSb.root.modeUid & 0xFFFF) : (ushort)(crSb.root.modeUid >> 16);
sbInformation.AppendFormat(Localization.Cram_Root_directory_size_0_bytes, crSb.root.Size).AppendLine();
uint rootSize = littleEndian ? crSb.root.sizeGid & 0xFFFFFF : crSb.root.sizeGid >> 8;
sbInformation.AppendFormat(Localization.Cram_Root_directory_mode_0, Convert.ToString(rootMode, 8)).AppendLine();
sbInformation.AppendFormat(Localization.Cram_Root_directory_size_0_bytes, rootSize).AppendLine();
information = sbInformation.ToString();

View File

@@ -36,23 +36,28 @@ public sealed partial class Cram
/// <summary>Converts a cramfs inode to a FileEntryInfo structure</summary>
/// <param name="inode">The cramfs inode</param>
/// <returns>FileEntryInfo with the file's metadata</returns>
static FileEntryInfo InodeToFileEntryInfo(Inode inode)
FileEntryInfo InodeToFileEntryInfo(Inode inode)
{
ushort mode = GetInodeMode(inode);
ushort uid = GetInodeUid(inode);
uint size = GetInodeSize(inode);
byte gid = GetInodeGid(inode);
var info = new FileEntryInfo
{
Attributes = FileAttributes.None,
BlockSize = 4096, // CramFS uses 4K pages
Length = inode.Size,
UID = inode.Uid,
GID = inode.Gid,
Mode = (uint)(inode.Mode & S_IPERM)
Length = size,
UID = uid,
GID = gid,
Mode = (uint)(mode & S_IPERM)
};
// CramFS doesn't store timestamps
// Links count is not stored in cramfs
// Determine file type from mode
var fileType = (ushort)(inode.Mode & S_IFMT);
var fileType = (ushort)(mode & S_IFMT);
switch(fileType)
{
@@ -73,9 +78,8 @@ public sealed partial class Cram
// For device files, Size field contains device number (i_rdev)
// Major = bits 8-15, Minor = bits 0-7
uint chrDev = inode.Size;
uint chrMajor = chrDev >> 8 & 0xFF;
uint chrMinor = chrDev & 0xFF;
uint chrMajor = size >> 8 & 0xFF;
uint chrMinor = size & 0xFF;
info.DeviceNo = (ulong)chrMajor << 32 | chrMinor;
break;
@@ -84,9 +88,8 @@ public sealed partial class Cram
// For device files, Size field contains device number (i_rdev)
// Major = bits 8-15, Minor = bits 0-7
uint blkDev = inode.Size;
uint blkMajor = blkDev >> 8 & 0xFF;
uint blkMinor = blkDev & 0xFF;
uint blkMajor = size >> 8 & 0xFF;
uint blkMinor = size & 0xFF;
info.DeviceNo = (ulong)blkMajor << 32 | blkMinor;
break;
@@ -105,7 +108,7 @@ public sealed partial class Cram
}
// Calculate blocks (size / block size, rounded up)
info.Blocks = (inode.Size + 4095) / 4096;
info.Blocks = (size + 4095) / 4096;
return info;
}

View File

@@ -75,9 +75,9 @@ public sealed partial class Cram
}
// Validate root inode is a directory
if(!IsDirectory(_superBlock.root.Mode))
if(!IsDirectory(GetInodeMode(_superBlock.root)))
{
AaruLogging.Debug(MODULE_NAME, "Root is not a directory, mode: 0x{0:X4}", _superBlock.root.Mode);
AaruLogging.Debug(MODULE_NAME, "Root is not a directory, mode: 0x{0:X4}", GetInodeMode(_superBlock.root));
return ErrorNumber.InvalidArgument;
}
@@ -232,8 +232,8 @@ public sealed partial class Cram
ErrorNumber LoadRootDirectory()
{
// Root directory offset is in the root inode
uint rootOffset = _superBlock.root.Offset << 2;
uint rootSize = _superBlock.root.Size;
uint rootOffset = GetInodeOffset(_superBlock.root) << 2;
uint rootSize = GetInodeSize(_superBlock.root);
AaruLogging.Debug(MODULE_NAME, "Root directory offset: {0}, size: {1}", rootOffset, rootSize);

View File

@@ -42,44 +42,28 @@ public sealed partial class Cram
/// <summary>CramFS inode structure</summary>
/// <remarks>
/// The inode uses bit fields:
/// - mode: 16 bits (file mode/permissions)
/// - uid: 16 bits (user ID)
/// - size: 24 bits (file size, or i_rdev for device files)
/// - gid: 8 bits (group ID)
/// - namelen: 6 bits (name length divided by 4, rounded up)
/// - offset: 26 bits (data offset divided by 4)
/// The inode uses bit fields that are laid out differently on big-endian vs little-endian systems:
/// Little-endian layout:
/// - modeUid: mode (16 bits) | uid (16 bits)
/// - sizeGid: size (24 bits) | gid (8 bits)
/// - namelenOffset: namelen (6 bits) | offset (26 bits)
/// Big-endian layout:
/// - modeUid: uid (16 bits) | mode (16 bits)
/// - sizeGid: gid (8 bits) | size (24 bits)
/// - namelenOffset: offset (26 bits) | namelen (6 bits)
/// </remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SwapEndian]
partial struct Inode
{
/// <summary>Lower 32 bits: mode (16 bits) | uid (16 bits)</summary>
/// <summary>Combined mode and uid field</summary>
public uint modeUid;
/// <summary>Middle 32 bits: size (24 bits) | gid (8 bits)</summary>
/// <summary>Combined size and gid field</summary>
public uint sizeGid;
/// <summary>Upper 32 bits: namelen (6 bits) | offset (26 bits)</summary>
/// <summary>Combined namelen and offset field</summary>
public uint namelenOffset;
/// <summary>Gets the file mode (permissions and type)</summary>
public ushort Mode => (ushort)(modeUid & 0xFFFF);
/// <summary>Gets the user ID</summary>
public ushort Uid => (ushort)(modeUid >> 16);
/// <summary>Gets the file size (or device number for device files)</summary>
public uint Size => sizeGid & 0xFFFFFF;
/// <summary>Gets the group ID</summary>
public byte Gid => (byte)(sizeGid >> 24);
/// <summary>Gets the name length (actual length = namelen * 4)</summary>
public byte NameLen => (byte)(namelenOffset & 0x3F);
/// <summary>Gets the data offset (actual offset = offset * 4)</summary>
public uint Offset => namelenOffset >> 6;
}
#endregion