From bc2d031abff5559e2c0f8fc5070642efcabf7611 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sat, 7 Feb 2026 23:44:12 +0000 Subject: [PATCH] Implement traversing and enumeration of AIX minidisk partitions. Fixes #7 --- Aaru.Partitions/AixMinidisk.cs | 286 +++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 Aaru.Partitions/AixMinidisk.cs diff --git a/Aaru.Partitions/AixMinidisk.cs b/Aaru.Partitions/AixMinidisk.cs new file mode 100644 index 000000000..74279991d --- /dev/null +++ b/Aaru.Partitions/AixMinidisk.cs @@ -0,0 +1,286 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : AixMinidisk.cs +// Author(s) : Natalia Portillo +// +// Component : Partitioning scheme plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages AIX minidisk partitions (VTOC). +// +// --[ 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 System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Logging; +using Marshal = Aaru.Helpers.Marshal; + +namespace Aaru.Partitions; + +/// +/// Implements decoding of the AIX minidisk (VTOC) partitioning scheme +[SuppressMessage("ReSharper", "InconsistentNaming")] +public sealed class AixMinidisk : IPartition +{ + const string MODULE_NAME = "AIX minidisk plugin"; + + /// AIX VTOC magic string "VTOC" + const uint VTOC_MAGIC = 0x56544F43; // "VTOC" in ASCII + + /// Block size for AIX minidisks + const int BLOCKSIZE = 512; + + /// Maximum number of minidisks in VTOC + const int MAX_MINIDISKS = 32; + + /// + public string Name => "AIX minidisk"; + + /// + public Guid Id => new("25ED1C9A-0FF0-49FC-9F43-34844B8309B8"); + + /// + public string Author => Authors.NATALIA_PORTILLO; + + /// + public bool GetInformation(IMediaImage imagePlugin, out List partitions, ulong sectorOffset) + { + partitions = []; + + // VTOC is typically at sector 1 (after boot0) + // But we need to find the AIX partition first if coming from MBR + ulong vtocSector = sectorOffset + 1; + + ErrorNumber errno = imagePlugin.ReadSector(vtocSector, false, out byte[] sector, out _); + + if(errno != ErrorNumber.NoError || sector.Length < BLOCKSIZE) + { + AaruLogging.Debug(MODULE_NAME, "Error reading VTOC sector: {0}", errno); + + return false; + } + + // Check for VTOC magic + var magic = (uint)(sector[0] << 24 | sector[1] << 16 | sector[2] << 8 | sector[3]); + + AaruLogging.Debug(MODULE_NAME, "VTOC magic = 0x{0:X8}, expected 0x{1:X8}", magic, VTOC_MAGIC); + + if(magic != VTOC_MAGIC) + { + AaruLogging.Debug(MODULE_NAME, "VTOC magic not found at sector {0}", vtocSector); + + return false; + } + + Vtoc1 vtoc = Marshal.ByteArrayToStructureLittleEndian(sector); + + AaruLogging.Debug(MODULE_NAME, + "vtoc.magic_string = \"{0}\"", + Encoding.ASCII.GetString(vtoc.magic_string).TrimEnd('\0')); + + AaruLogging.Debug(MODULE_NAME, "vtoc.version = {0}", vtoc.version); + AaruLogging.Debug(MODULE_NAME, "vtoc.seq_num = {0}", vtoc.seq_num); + AaruLogging.Debug(MODULE_NAME, "vtoc.bootsize = {0}", vtoc.bootsize); + AaruLogging.Debug(MODULE_NAME, "vtoc.numbadblks = {0}", vtoc.numbadblks); + AaruLogging.Debug(MODULE_NAME, "vtoc.badblkoff = {0}", vtoc.badblkoff); + + // Read VTOC2 and VTOC3 for mount string table + errno = imagePlugin.ReadSector(vtocSector + 1, false, out byte[] vtoc2Sector, out _); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading VTOC2 sector: {0}", errno); + + return false; + } + + errno = imagePlugin.ReadSector(vtocSector + 2, false, out byte[] vtoc3Sector, out _); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading VTOC3 sector: {0}", errno); + + return false; + } + + // Build the string table from VTOC2 and VTOC3 + // VTOC2: byte 0 is seq_num, bytes 1-511 are first part of string table + // VTOC3: bytes 0-506 are second part, byte 507 is seq_num, bytes 508-511 are checksum + var stringTable = new byte[511 + 507]; // V2_STRTABLEN + V3_STRTABLEN + Array.Copy(vtoc2Sector, 1, stringTable, 0, 511); + Array.Copy(vtoc3Sector, 0, stringTable, 511, 507); + + ulong counter = 0; + + for(var i = 0; i < MAX_MINIDISKS; i++) + { + Minidisk md = vtoc.mini[i]; + + // Check for end marker + if(md.type == MD_END) + { + AaruLogging.Debug(MODULE_NAME, "End of minidisk table at entry {0}", i); + + break; + } + + AaruLogging.Debug(MODULE_NAME, "vtoc.mini[{0}].s_block = {1}", i, md.s_block); + AaruLogging.Debug(MODULE_NAME, "vtoc.mini[{0}].num_blks = {1}", i, md.num_blks); + AaruLogging.Debug(MODULE_NAME, "vtoc.mini[{0}].type = {1} ({2})", i, md.type, TypeToString(md.type)); + AaruLogging.Debug(MODULE_NAME, "vtoc.mini[{0}].mdisk_id = {1}", i, md.mdisk_id); + AaruLogging.Debug(MODULE_NAME, "vtoc.mini[{0}].str_index = {1}", i, md.str_index); + + // Skip entries with no blocks + if(md.num_blks == 0) continue; + + // Skip reserved and bad block entries + if(md.type is MD_BAD_BLOCK or MD_UNALLOC) continue; + + // Get mount point string from string table + var mountPoint = ""; + + if(md.str_index > 0 && md.str_index < stringTable.Length) + { + int strEnd = Array.IndexOf(stringTable, 0, md.str_index); + int strLen = strEnd >= 0 ? strEnd - md.str_index : stringTable.Length - md.str_index; + mountPoint = Encoding.ASCII.GetString(stringTable, md.str_index, strLen); + } + + var part = new Partition + { + Start = md.s_block * BLOCKSIZE / imagePlugin.Info.SectorSize, + Offset = md.s_block * BLOCKSIZE, + Length = md.num_blks * BLOCKSIZE / imagePlugin.Info.SectorSize, + Size = md.num_blks * BLOCKSIZE, + Type = TypeToString(md.type), + Sequence = counter, + Scheme = Name, + Name = string.IsNullOrEmpty(mountPoint) ? $"hd{md.mdisk_id & 0x1F}" : mountPoint + }; + + AaruLogging.Debug(MODULE_NAME, + "Partition {0}: start={1}, length={2}, type={3}, name={4}", + counter, + part.Start, + part.Length, + part.Type, + part.Name); + + partitions.Add(part); + counter++; + } + + return partitions.Count > 0; + } + + static string TypeToString(byte type) => type switch + { + MD_END => "End marker", + MD_PAGE => "AIX paging space", + MD_DUMP => "AIX dump space", + MD_UNALLOC => "Unallocated", + MD_AIX_BOOT => "AIX boot", + MD_AIX_NOBOOT => "AIX filesystem", + MD_BAD_BLOCK => "Bad block table", + _ => $"Unknown (0x{type:X2})" + }; + +#region Constants + + /// End of VTOC table marker + const byte MD_END = 0; + + /// Page space minidisk + const byte MD_PAGE = 1; + + /// Dump space minidisk + const byte MD_DUMP = 2; + + /// Free space for AIX + const byte MD_UNALLOC = 3; + + /// AIX minidisk containing /unixtext + const byte MD_AIX_BOOT = 8; + + /// Any other AIX minidisk + const byte MD_AIX_NOBOOT = 9; + + /// Bad block table minidisk + const byte MD_BAD_BLOCK = 0x0E; + +#endregion + +#region Nested type: Minidisk + + /// AIX minidisk entry structure + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Minidisk + { + /// Starting block number + public readonly uint s_block; + /// Length of minidisk in blocks + public readonly uint num_blks; + /// Type of minidisk + public readonly byte type; + /// Minidisk ID number + public readonly byte mdisk_id; + /// Index into string table + public readonly ushort str_index; + } + +#endregion + +#region Nested type: Vtoc1 + + /// AIX VTOC sector 1 structure + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Vtoc1 + { + /// "VTOC" magic string + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public readonly byte[] magic_string; + /// Version number + public readonly byte version; + /// Sequence number (must match others) + public readonly byte seq_num; + /// Max length of boot 1 in disk blocks + public readonly ushort bootsize; + /// Number of bad blocks (ST506 only) + public readonly ushort numbadblks; + /// Offset within the bad block minidisk + public readonly ushort badblkoff; + /// Spare (to put mini[] on 8 byte boundary) + public readonly int vt_spare; + /// Minidisk entries + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_MINIDISKS)] + public readonly Minidisk[] mini; + } + +#endregion +} \ No newline at end of file