// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : BSD.cs // Author(s) : Natalia Portillo // // Component : Partitioning scheme plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages BSD disklabels. // // --[ 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-2025 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using Aaru.CommonTypes; using Aaru.CommonTypes.Attributes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru.Logging; using Marshal = Aaru.Helpers.Marshal; namespace Aaru.Partitions; /// /// Implements decoding of BSD disklabels [SuppressMessage("ReSharper", "UnusedMember.Local")] public sealed partial class BSD : IPartition { const uint DISK_MAGIC = 0x82564557; const uint DISK_CIGAM = 0x57455682; /// Maximum size of a disklabel with 22 partitions const uint MAX_LABEL_SIZE = 500; const string MODULE_NAME = "BSD disklabel plugin"; /// Known sector locations for BSD disklabel readonly ulong[] _labelLocations = [0, 1, 2, 9]; /// Known byte offsets for BSD disklabel readonly uint[] _labelOffsets = [0, 9, 64, 128, 516]; #region IPartition Members /// public string Name => Localization.BSD_Name; /// public Guid Id => new("246A6D93-4F1A-1F8A-344D-50187A5513A9"); /// public string Author => Authors.NATALIA_PORTILLO; /// public bool GetInformation(IMediaImage imagePlugin, out List partitions, ulong sectorOffset) { partitions = []; uint run = (MAX_LABEL_SIZE + _labelOffsets.Last()) / imagePlugin.Info.SectorSize; if((MAX_LABEL_SIZE + _labelOffsets.Last()) % imagePlugin.Info.SectorSize > 0) run++; var dl = new DiskLabel(); var found = false; foreach(ulong location in _labelLocations) { if(location + run + sectorOffset >= imagePlugin.Info.Sectors) return false; ErrorNumber errno = imagePlugin.ReadSectors(location + sectorOffset, false, run, out byte[] tmp, out _); if(errno != ErrorNumber.NoError) continue; foreach(uint offset in _labelOffsets) { var sector = new byte[MAX_LABEL_SIZE]; if(offset + MAX_LABEL_SIZE > tmp.Length) break; Array.Copy(tmp, offset, sector, 0, MAX_LABEL_SIZE); dl = Marshal.ByteArrayToStructureLittleEndian(sector); AaruLogging.Debug(MODULE_NAME, Localization .BSD_GetInformation_dl_magic_on_sector_0_at_offset_1_equals_2_X8_expected_3_X8, location + sectorOffset, offset, dl.d_magic, DISK_MAGIC); if((dl.d_magic != DISK_MAGIC || dl.d_magic2 != DISK_MAGIC) && (dl.d_magic != DISK_CIGAM || dl.d_magic2 != DISK_CIGAM)) continue; found = true; break; } if(found) break; } if(!found) return false; if(dl is { d_magic: DISK_CIGAM, d_magic2: DISK_CIGAM }) dl = dl.SwapEndian(); AaruLogging.Debug(MODULE_NAME, "dl.d_type = {0}", dl.d_type); AaruLogging.Debug(MODULE_NAME, "dl.d_subtype = {0}", dl.d_subtype); AaruLogging.Debug(MODULE_NAME, "dl.d_typename = {0}", StringHandlers.CToString(dl.d_typename)); AaruLogging.Debug(MODULE_NAME, "dl.d_packname = {0}", StringHandlers.CToString(dl.d_packname)); AaruLogging.Debug(MODULE_NAME, "dl.d_secsize = {0}", dl.d_secsize); AaruLogging.Debug(MODULE_NAME, "dl.d_nsectors = {0}", dl.d_nsectors); AaruLogging.Debug(MODULE_NAME, "dl.d_ntracks = {0}", dl.d_ntracks); AaruLogging.Debug(MODULE_NAME, "dl.d_ncylinders = {0}", dl.d_ncylinders); AaruLogging.Debug(MODULE_NAME, "dl.d_secpercyl = {0}", dl.d_secpercyl); AaruLogging.Debug(MODULE_NAME, "dl.d_secperunit = {0}", dl.d_secperunit); AaruLogging.Debug(MODULE_NAME, "dl.d_sparespertrack = {0}", dl.d_sparespertrack); AaruLogging.Debug(MODULE_NAME, "dl.d_sparespercyl = {0}", dl.d_sparespercyl); AaruLogging.Debug(MODULE_NAME, "dl.d_acylinders = {0}", dl.d_acylinders); AaruLogging.Debug(MODULE_NAME, "dl.d_rpm = {0}", dl.d_rpm); AaruLogging.Debug(MODULE_NAME, "dl.d_interleave = {0}", dl.d_interleave); AaruLogging.Debug(MODULE_NAME, "dl.d_trackskew = {0}", dl.d_trackskew); AaruLogging.Debug(MODULE_NAME, "dl.d_cylskeew = {0}", dl.d_cylskeew); AaruLogging.Debug(MODULE_NAME, "dl.d_headswitch = {0}", dl.d_headswitch); AaruLogging.Debug(MODULE_NAME, "dl.d_trkseek = {0}", dl.d_trkseek); AaruLogging.Debug(MODULE_NAME, "dl.d_flags = {0}", dl.d_flags); AaruLogging.Debug(MODULE_NAME, "dl.d_drivedata[0] = {0}", dl.d_drivedata[0]); AaruLogging.Debug(MODULE_NAME, "dl.d_drivedata[1] = {0}", dl.d_drivedata[1]); AaruLogging.Debug(MODULE_NAME, "dl.d_drivedata[2] = {0}", dl.d_drivedata[2]); AaruLogging.Debug(MODULE_NAME, "dl.d_drivedata[3] = {0}", dl.d_drivedata[3]); AaruLogging.Debug(MODULE_NAME, "dl.d_drivedata[4] = {0}", dl.d_drivedata[4]); AaruLogging.Debug(MODULE_NAME, "dl.d_spare[0] = {0}", dl.d_spare[0]); AaruLogging.Debug(MODULE_NAME, "dl.d_spare[1] = {0}", dl.d_spare[1]); AaruLogging.Debug(MODULE_NAME, "dl.d_spare[2] = {0}", dl.d_spare[2]); AaruLogging.Debug(MODULE_NAME, "dl.d_spare[3] = {0}", dl.d_spare[3]); AaruLogging.Debug(MODULE_NAME, "dl.d_spare[4] = {0}", dl.d_spare[4]); AaruLogging.Debug(MODULE_NAME, "dl.d_magic2 = 0x{0:X8}", dl.d_magic2); AaruLogging.Debug(MODULE_NAME, "dl.d_checksum = 0x{0:X8}", dl.d_checksum); AaruLogging.Debug(MODULE_NAME, "dl.d_npartitions = {0}", dl.d_npartitions); AaruLogging.Debug(MODULE_NAME, "dl.d_bbsize = {0}", dl.d_bbsize); AaruLogging.Debug(MODULE_NAME, "dl.d_sbsize = {0}", dl.d_sbsize); ulong counter = 0; var addSectorOffset = false; for(var i = 0; i < dl.d_npartitions && i < 22; i++) { AaruLogging.Debug(MODULE_NAME, "dl.d_partitions[i].p_offset = {0}", dl.d_partitions[i].p_offset); AaruLogging.Debug(MODULE_NAME, "dl.d_partitions[i].p_size = {0}", dl.d_partitions[i].p_size); AaruLogging.Debug(MODULE_NAME, "dl.d_partitions[i].p_fstype = {0} ({1})", dl.d_partitions[i].p_fstype, FSTypeToString(dl.d_partitions[i].p_fstype)); var part = new Partition { Start = dl.d_partitions[i].p_offset * dl.d_secsize / imagePlugin.Info.SectorSize, Offset = dl.d_partitions[i].p_offset * dl.d_secsize, Length = dl.d_partitions[i].p_size * dl.d_secsize / imagePlugin.Info.SectorSize, Size = dl.d_partitions[i].p_size * dl.d_secsize, Type = FSTypeToString(dl.d_partitions[i].p_fstype), Sequence = counter, Scheme = Name }; if(dl.d_partitions[i].p_fstype == fsType.Unused) continue; // Crude and dirty way to know if the disklabel is relative to its parent partition... if(dl.d_partitions[i].p_offset < sectorOffset && !addSectorOffset) addSectorOffset = true; if(addSectorOffset) { part.Start += sectorOffset; part.Offset += sectorOffset * imagePlugin.Info.SectorSize; } AaruLogging.Debug(MODULE_NAME, "part.start = {0}", part.Start); AaruLogging.Debug(MODULE_NAME, Localization.BSD_GetInformation_Adding_it); partitions.Add(part); counter++; } return partitions.Count > 0; } #endregion internal static string FSTypeToString(fsType typ) { return typ switch { fsType.Unused => Localization.Unused_entry, fsType.Swap => Localization.Swap_partition, fsType.V6 => Localization.UNIX_6th_Edition, fsType.V7 => Localization.UNIX_7th_Edition, fsType.SystemV => Localization.UNIX_System_V, fsType.V7_1K => Localization.UNIX_7th_Edition_with_1K_blocks, fsType.V8 => Localization.UNIX_8th_Edition_with_4K_blocks, fsType.BSDFFS => Localization._4_2_BSD_Fast_File_System, fsType.BSDLFS => Localization._4_4_LFS, fsType.HPFS => Localization.HPFS, fsType.ISO9660 => Localization.ISO9660, fsType.Boot or fsType.SysVBoot => Localization.Boot, fsType.AFFS => Localization.Amiga_FFS, fsType.HFS => Localization.Apple_HFS, fsType.ADVfs => Localization.Digital_Advanced_File_System, fsType.LSMpublic => Localization.Digital_LSM_Public_Region, fsType.LSMprivate => Localization.Digital_LSM_Private_Region, fsType.LSMsimple => Localization.Digital_LSM_Simple_Disk, fsType.CCD => Localization.Concatenated_disk, fsType.JFS2 => Localization.IBM_JFS2, fsType.HAMMER => Localization.Hammer, fsType.HAMMER2 => Localization.Hammer2, fsType.UDF => Localization.UDF, fsType.EFS => Localization.EFS, fsType.ZFS => Localization.ZFS, fsType.NANDFS => Localization.FreeBSD_nandfs, fsType.MSDOS => Localization.FAT, fsType.Other => Localization.Other_or_unknown, _ => Localization.Unknown_partition_type }; } #region Nested type: BSDPartition [StructLayout(LayoutKind.Sequential, Pack = 1)] [SwapEndian] partial struct BSDPartition { /// Sectors in partition public uint p_size; /// Starting sector public uint p_offset; /// Fragment size public uint p_fsize; /// Filesystem type, public fsType p_fstype; /// Fragment size public byte p_frag; /// Cylinder per group public ushort p_cpg; } #endregion #region Nested type: dFlags /// Drive flags [SuppressMessage("ReSharper", "InconsistentNaming")] [Flags] enum dFlags : uint { /// Removable media Removable = 0x01, /// Drive supports ECC ECC = 0x02, /// Drive supports bad sector forwarding BadSectorForward = 0x04, /// Disk emulator RAMDisk = 0x08, /// Can do back to back transfer Chain = 0x10, /// Dynamic geometry device DynamicGeometry = 0x20 } #endregion #region Nested type: DiskLabel [StructLayout(LayoutKind.Sequential, Pack = 1)] [SwapEndian] partial struct DiskLabel { /// /// /// public uint d_magic; /// /// /// public dType d_type; /// Disk subtype public ushort d_subtype; /// Type name [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] d_typename; /// Pack identifier [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] d_packname; /// Bytes per sector public uint d_secsize; /// Sectors per track public uint d_nsectors; /// Tracks per cylinder public uint d_ntracks; /// Cylinders per unit public uint d_ncylinders; /// Sectors per cylinder public uint d_secpercyl; /// Sectors per unit public uint d_secperunit; /// Spare sectors per track public ushort d_sparespertrack; /// Spare sectors per cylinder public ushort d_sparespercyl; /// Alternate cylinders public uint d_acylinders; /// Rotational speed public ushort d_rpm; /// Hardware sector interleave public ushort d_interleave; /// Sector 0 skew per track public ushort d_trackskew; /// Sector 0 sker per cylinder public ushort d_cylskeew; /// Head switch time in microseconds public uint d_headswitch; /// Track to track seek in microseconds public uint d_trkseek; /// /// /// public dFlags d_flags; /// Drive-specific information [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public uint[] d_drivedata; /// Reserved [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public uint[] d_spare; /// again public uint d_magic2; /// XOR of data public ushort d_checksum; /// How many partitions public ushort d_npartitions; /// Size of boot area in bytes public uint d_bbsize; /// Maximum size of superblock in bytes public uint d_sbsize; /// Partitions [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] public BSDPartition[] d_partitions; } #endregion #region Nested type: dType /// Drive type [SuppressMessage("ReSharper", "InconsistentNaming")] enum dType : ushort { /// SMD, XSMD SMD = 1, /// MSCP MSCP = 2, /// Other DEC (rk, rl) DEC = 3, /// SCSI SCSI = 4, /// ESDI ESDI = 5, /// ST506 et al ST506 = 6, /// CS/80 on HP-IB HPIB = 7, /// HP Fiber-link HPFL = 8, /// Floppy FLOPPY = 10, /// Concatenated disk CCD = 11, /// uvnode pseudo-disk VND = 12, /// DiskOnChip DOC2K = 13, /// ATAPI ATAPI = 13, /// CMU RAIDframe RAID = 14, /// Logical disk LD = 15, /// IBM JFS 2 JFS2 = 16, /// Cryptographic pseudo-disk CGD = 17, /// Vinum volume VINUM = 18, /// Flash memory devices FLASH = 19, /// Device-mapper pseudo-disk devices DM = 20, /// Rump virtual disk RUMPD = 21, /// Memory disk MD = 22 } #endregion #region Nested type: fsType /// Filesystem type [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "UnusedMember.Global")] internal enum fsType : byte { /// Unused entry Unused = 0, /// Swap partition Swap = 1, /// UNIX 6th Edition V6 = 2, /// UNIX 7th Edition V7 = 3, /// UNIX System V SystemV = 4, /// UNIX 7th Edition with 1K blocks V7_1K = 5, /// UNIX 8th Edition with 4K blocks V8 = 6, /// 4.2BSD Fast File System BSDFFS = 7, /// MS-DOS filesystem MSDOS = 8, /// 4.4LFS BSDLFS = 9, /// In use, unknown or unsupported Other = 10, /// HPFS HPFS = 11, /// ISO9660 ISO9660 = 12, /// Boot partition Boot = 13, /// Amiga FFS AFFS = 14, /// Apple HFS HFS = 15, /// Acorn ADFS FileCore = 16, /// Digital Advanced File System ADVfs = 16, /// Digital LSM Public Region LSMpublic = 17, /// Linux ext2 ext2 = 17, /// Digital LSM Private Region LSMprivate = 18, /// NTFS NTFS = 18, /// Digital LSM Simple Disk LSMsimple = 19, /// RAIDframe component RAID = 19, /// Concatenated disk component CCD = 20, /// IBM JFS2 JFS2 = 21, /// Apple UFS AppleUFS = 22, /// Hammer filesystem HAMMER = 22, /// Hammer2 filesystem HAMMER2 = 23, /// UDF UDF = 24, /// System V Boot filesystem SysVBoot = 25, /// EFS EFS = 26, /// ZFS ZFS = 27, /// NiLFS NILFS = 27, /// Cryptographic disk CGD = 28, /// MINIX v3 MINIX = 29, /// FreeBSD nandfs NANDFS = 30 } #endregion }