[efs] Add missing on-disk structures.

This commit is contained in:
2026-02-07 21:18:17 +00:00
parent dc33049af2
commit b769f3f79d
3 changed files with 355 additions and 22 deletions

View File

@@ -32,8 +32,63 @@ namespace Aaru.Filesystems;
/// <summary>Implements identification for the SGI Extent FileSystem</summary>
public sealed partial class EFS
{
const uint EFS_MAGIC = 0x00072959;
/// <summary>Original EFS magic number.</summary>
const uint EFS_MAGIC = 0x00072959;
/// <summary>New EFS magic number (IRIX 3.3+).</summary>
const uint EFS_MAGIC_NEW = 0x0007295A;
/// <summary>Filesystem type identifier.</summary>
const string FS_TYPE = "efs";
/// <summary>Number of directly mappable extents in an inode.</summary>
const int EFS_DIRECTEXTENTS = 12;
/// <summary>Maximum number of basic blocks in an indirect extent.</summary>
const int EFS_MAXINDIRBBS = 64;
/// <summary>Maximum number of extents per inode.</summary>
const int EFS_MAXEXTENTS = 32767;
/// <summary>Maximum length of a single extent in basic blocks (256 - 8).</summary>
const int EFS_MAXEXTENTLEN = 248;
/// <summary>Inode size in bytes (128).</summary>
const int EFS_INODE_SIZE = 128;
/// <summary>Inode size shift (log2 of 128 = 7).</summary>
const int EFS_EFSINOSHIFT = 7;
/// <summary>Basic block size (512 bytes).</summary>
const int EFS_BBSIZE = 512;
/// <summary>Directory block size (same as basic block).</summary>
const int EFS_DIRBSIZE = EFS_BBSIZE;
/// <summary>Directory block magic number.</summary>
const ushort EFS_DIRBLK_MAGIC = 0xBEEF;
/// <summary>Maximum filename length (255).</summary>
const int EFS_MAXNAMELEN = 255;
/// <summary>Minimum directory entry size (6 bytes: 4 for inum + 1 for namelen + 1 for min name).</summary>
const int EFS_DENTSIZE = 6;
/// <summary>Size of directory block header.</summary>
const int EFS_DIRBLK_HEADERSIZE = 4;
/// <summary>Superblock location in basic blocks.</summary>
const int EFS_SUPERBB = 1;
/// <summary>Bitmap location in basic blocks (for non-grown filesystems).</summary>
const int EFS_BITMAPBB = 2;
/// <summary>Root inode number.</summary>
const uint EFS_ROOTINO = 2;
/// <summary>Number of inodes per basic block.</summary>
const int EFS_INOPBB = EFS_BBSIZE / EFS_INODE_SIZE;
/// <summary>Maximum inline data size (96 bytes = 12 extents * 8 bytes).</summary>
const int EFS_MAX_INLINE = EFS_DIRECTEXTENTS * 8;
}

View File

@@ -0,0 +1,135 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Enums.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Extent 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
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
namespace Aaru.Filesystems;
public sealed partial class EFS
{
#region Nested type: FileType
/// <summary>EFS file type flags (from di_mode).</summary>
[Flags]
[SuppressMessage("ReSharper", "InconsistentNaming")]
enum FileType : ushort
{
/// <summary>Type mask.</summary>
IFMT = 0xF000,
/// <summary>Named pipe (FIFO).</summary>
IFIFO = 0x1000,
/// <summary>Character special device.</summary>
IFCHR = 0x2000,
/// <summary>Character special link.</summary>
IFCHRLNK = 0x3000,
/// <summary>Directory.</summary>
IFDIR = 0x4000,
/// <summary>Block special device.</summary>
IFBLK = 0x6000,
/// <summary>Block special link.</summary>
IFBLKLNK = 0x7000,
/// <summary>Regular file.</summary>
IFREG = 0x8000,
/// <summary>Symbolic link.</summary>
IFLNK = 0xA000,
/// <summary>Socket.</summary>
IFSOCK = 0xC000
}
#endregion
#region Nested type: FilePermissions
/// <summary>EFS file permission flags (from di_mode).</summary>
[Flags]
[SuppressMessage("ReSharper", "InconsistentNaming")]
enum FilePermissions : ushort
{
/// <summary>Set user ID on execution.</summary>
ISUID = 0x0800,
/// <summary>Set group ID on execution.</summary>
ISGID = 0x0400,
/// <summary>Sticky bit (save text / restricted deletion).</summary>
ISVTX = 0x0200,
/// <summary>Owner read permission.</summary>
IRUSR = 0x0100,
/// <summary>Owner write permission.</summary>
IWUSR = 0x0080,
/// <summary>Owner execute permission.</summary>
IXUSR = 0x0040,
/// <summary>Group read permission.</summary>
IRGRP = 0x0020,
/// <summary>Group write permission.</summary>
IWGRP = 0x0010,
/// <summary>Group execute permission.</summary>
IXGRP = 0x0008,
/// <summary>Others read permission.</summary>
IROTH = 0x0004,
/// <summary>Others write permission.</summary>
IWOTH = 0x0002,
/// <summary>Others execute permission.</summary>
IXOTH = 0x0001
}
#endregion
#region Nested type: InodeVersion
/// <summary>EFS inode version values.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
enum InodeVersion : byte
{
/// <summary>Standard EFS inode.</summary>
EFS_IVER_EFS = 0,
/// <summary>AFS special inode.</summary>
EFS_IVER_AFSSPEC = 1,
/// <summary>AFS normal inode.</summary>
EFS_IVER_AFSINO = 2
}
#endregion
#region Nested type: DirtyFlags
/// <summary>Values for sb_dirty field indicating filesystem state.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
enum DirtyFlags : short
{
/// <summary>Filesystem is clean and unmounted.</summary>
EFS_CLEAN = 0x0000,
/// <summary>Mounted a dirty filesystem (root only).</summary>
EFS_ACTIVEDIRT = 0x0BAD,
/// <summary>Filesystem is mounted and clean.</summary>
EFS_ACTIVE = 0x7777,
/// <summary>Filesystem is dirty.</summary>
EFS_DIRTY = 0x1234
}
#endregion
}

View File

@@ -38,57 +38,200 @@ public sealed partial class EFS
{
#region Nested type: Superblock
/// <summary>EFS Superblock structure, 92 bytes. Located at basic block 1.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SwapEndian]
partial struct Superblock
{
/* 0: fs size incl. bb 0 (in bb) */
/// <summary>0x00: Filesystem size including bb 0, in basic blocks.</summary>
public int sb_size;
/* 4: first cg offset (in bb) */
/// <summary>0x04: First cylinder group offset, in basic blocks.</summary>
public int sb_firstcg;
/* 8: cg size (in bb) */
/// <summary>0x08: Cylinder group size, in basic blocks.</summary>
public int sb_cgfsize;
/* 12: inodes/cg (in bb) */
/// <summary>0x0C: Inodes per cylinder group, in basic blocks.</summary>
public short sb_cgisize;
/* 14: geom: sectors/track */
/// <summary>0x0E: Geometry: sectors per track.</summary>
public short sb_sectors;
/* 16: geom: heads/cylinder (unused) */
/// <summary>0x10: Geometry: heads per cylinder (unused).</summary>
public short sb_heads;
/* 18: num of cg's in the filesystem */
/// <summary>0x12: Number of cylinder groups in filesystem.</summary>
public short sb_ncg;
/* 20: non-0 indicates fsck required */
/// <summary>0x14: Non-zero indicates fsck required.</summary>
public short sb_dirty;
/* 22: */
/// <summary>0x16: Padding.</summary>
public short sb_pad0;
/* 24: superblock ctime */
/// <summary>0x18: Superblock creation/modification time.</summary>
public int sb_time;
/* 28: magic [0] */
/// <summary>0x1C: Magic number (EFS_MAGIC or EFS_MAGIC_NEW).</summary>
public uint sb_magic;
/* 32: name of filesystem */
/// <summary>0x20: Filesystem name, 6 bytes.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] sb_fname;
/* 38: name of filesystem pack */
/// <summary>0x26: Filesystem pack name, 6 bytes.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] sb_fpack;
/* 44: bitmap size (in bytes) */
/// <summary>0x2C: Bitmap size in bytes.</summary>
public int sb_bmsize;
/* 48: total free data blocks */
/// <summary>0x30: Total free data blocks.</summary>
public int sb_tfree;
/* 52: total free inodes */
/// <summary>0x34: Total free inodes.</summary>
public int sb_tinode;
/* 56: bitmap offset (grown fs) */
/// <summary>0x38: Bitmap location (for grown filesystems).</summary>
public int sb_bmblock;
/* 62: repl. superblock offset */
/// <summary>0x3C: Replicated superblock location.</summary>
public int sb_replsb;
/* 64: last allocated inode */
/// <summary>0x40: Last allocated inode.</summary>
public int sb_lastinode;
/* 68: unused */
/// <summary>0x44: Spare/unused, 20 bytes (must be zero).</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] sb_spare;
/* 88: checksum (all above) */
/// <summary>0x58: Checksum of all above fields.</summary>
public uint sb_checksum;
}
#endregion
#region Nested type: Extent
/// <summary>
/// EFS Extent structure, 8 bytes. Describes a contiguous range of blocks. The structure uses bit fields packed
/// into two 32-bit words.
/// </summary>
/// <remarks>
/// Layout (big-endian):
/// <list type="bullet">
/// <item>Bits 63-56: Magic (must be 0)</item>
/// <item>Bits 55-32: Block number (24 bits)</item>
/// <item>Bits 31-24: Length in basic blocks (8 bits)</item>
/// <item>Bits 23-0: Logical offset into file (24 bits)</item>
/// </list>
/// </remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SwapEndian]
partial struct Extent
{
/// <summary>First 32 bits: magic (8 bits, must be 0) + block number (24 bits).</summary>
public uint ex_magic_bn;
/// <summary>Second 32 bits: length (8 bits) + logical offset (24 bits).</summary>
public uint ex_length_offset;
/// <summary>Gets the magic number (must be 0 for valid extent).</summary>
public byte Magic => (byte)(ex_magic_bn >> 24 & 0xFF);
/// <summary>Gets the starting block number (24 bits).</summary>
public uint BlockNumber => ex_magic_bn & 0x00FFFFFF;
/// <summary>Gets the extent length in basic blocks (8 bits, max 248).</summary>
public byte Length => (byte)(ex_length_offset >> 24 & 0xFF);
/// <summary>Gets the logical block offset into file (24 bits).</summary>
public uint Offset => ex_length_offset & 0x00FFFFFF;
}
#endregion
#region Nested type: DeviceNumbers
/// <summary>Device numbers for character/block special files. Used in inode di_u union.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SwapEndian]
partial struct DeviceNumbers
{
/// <summary>Old-style device number (16 bits).</summary>
public ushort odev;
/// <summary>Padding.</summary>
public ushort pad;
/// <summary>New-style (extended) device number (32 bits).</summary>
public uint ndev;
}
#endregion
#region Nested type: Inode
/// <summary>EFS on-disk inode structure, exactly 128 bytes.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SwapEndian]
partial struct Inode
{
/// <summary>0x00: Mode and type of file.</summary>
public ushort di_mode;
/// <summary>0x02: Number of links to file.</summary>
public short di_nlink;
/// <summary>0x04: Owner's user ID.</summary>
public ushort di_uid;
/// <summary>0x06: Owner's group ID.</summary>
public ushort di_gid;
/// <summary>0x08: Number of bytes in file.</summary>
public int di_size;
/// <summary>0x0C: Time last accessed (Unix timestamp).</summary>
public int di_atime;
/// <summary>0x10: Time last modified (Unix timestamp).</summary>
public int di_mtime;
/// <summary>0x14: Time created/changed (Unix timestamp).</summary>
public int di_ctime;
/// <summary>0x18: Generation number.</summary>
public uint di_gen;
/// <summary>0x1C: Number of extents.</summary>
public short di_numextents;
/// <summary>0x1E: Version of inode (0=EFS, 1=AFS special, 2=AFS normal).</summary>
public byte di_version;
/// <summary>0x1F: Spare byte (used by AFS).</summary>
public byte di_spare;
/// <summary>0x20: Direct extents array, 12 extents * 8 bytes = 96 bytes.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = EFS_DIRECTEXTENTS)]
public Extent[] di_extents;
}
#endregion
#region Nested type: DirectoryBlock
/// <summary>EFS directory block header, 4 bytes. The rest of the block contains directory entries.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SwapEndian]
partial struct DirectoryBlock
{
/// <summary>0x00: Magic number (0xBEEF).</summary>
public ushort magic;
/// <summary>0x02: Offset to first used directory entry byte.</summary>
public byte firstused;
/// <summary>0x03: Number of offset slots in directory block.</summary>
public byte slots;
}
#endregion
#region Nested type: DirectoryEntry
/// <summary>
/// EFS directory entry structure. Variable length due to name. The inode number is stored as two 16-bit values
/// for alignment reasons.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SwapEndian]
partial struct DirectoryEntry
{
/// <summary>0x00: Inode number high word (big-endian: high 16 bits).</summary>
public ushort d_inum_high;
/// <summary>0x02: Inode number low word (big-endian: low 16 bits).</summary>
public ushort d_inum_low;
/// <summary>0x04: Length of name string.</summary>
public byte d_namelen;
// Followed by d_name[d_namelen] - variable length name
/// <summary>Gets the full inode number (big-endian format).</summary>
public uint InodeNumber => (uint)(d_inum_high << 16 | d_inum_low);
}
#endregion
}