// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : AppleHFSPlus.cs // Author(s) : Natalia Portillo // // Component : Apple Hierarchical File System Plus plugin. // // --[ Description ] ---------------------------------------------------------- // // Identifies the Apple Hierarchical File System Plus and shows information. // // --[ 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-2017 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using DiscImageChef.CommonTypes; using DiscImageChef.Console; namespace DiscImageChef.Filesystems { // Information from Apple TechNote 1150: https://developer.apple.com/legacy/library/technotes/tn/tn1150.html public class AppleHFSPlus : Filesystem { /// /// "BD", HFS magic /// const ushort HFS_MAGIC = 0x4244; /// /// "H+", HFS+ magic /// const ushort HFSP_MAGIC = 0x482B; /// /// "HX", HFSX magic /// const ushort HFSX_MAGIC = 0x4858; public AppleHFSPlus() { Name = "Apple HFS+ filesystem"; PluginUUID = new Guid("36405F8D-0D26-6EBE-436F-62F0586B4F08"); CurrentEncoding = Encoding.UTF8; } public AppleHFSPlus(ImagePlugins.ImagePlugin imagePlugin, Partition partition, Encoding encoding) { Name = "Apple HFS+ filesystem"; PluginUUID = new Guid("36405F8D-0D26-6EBE-436F-62F0586B4F08"); if(encoding == null) CurrentEncoding = Encoding.UTF8; } public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, Partition partition) { if((2 + partition.Start) >= partition.End) return false; ushort drSigWord; ushort xdrStABNt; ushort drAlBlSt; uint drAlBlkSiz; byte[] vh_sector; ulong hfsp_offset; uint sectorsToRead = 0x800 / imagePlugin.ImageInfo.sectorSize; if(0x800 % imagePlugin.ImageInfo.sectorSize > 0) sectorsToRead++; vh_sector = imagePlugin.ReadSectors(partition.Start, sectorsToRead); // Read volume header, of HFS Wrapper MDB drSigWord = BigEndianBitConverter.ToUInt16(vh_sector, 0x400); // Check for HFS Wrapper MDB if(drSigWord == HFS_MAGIC) // "BD" { drSigWord = BigEndianBitConverter.ToUInt16(vh_sector, 0x47C); // Read embedded HFS+ signature if(drSigWord == HFSP_MAGIC) // "H+" { xdrStABNt = BigEndianBitConverter.ToUInt16(vh_sector, 0x47E); // Starting block number of embedded HFS+ volume drAlBlkSiz = BigEndianBitConverter.ToUInt32(vh_sector, 0x414); // Block size drAlBlSt = BigEndianBitConverter.ToUInt16(vh_sector, 0x41C); // Start of allocated blocks (in 512-byte/block) hfsp_offset = (ulong)(((drAlBlSt * 512) + (xdrStABNt * drAlBlkSiz)) / imagePlugin.GetSectorSize()); } else { hfsp_offset = 0; } } else { hfsp_offset = 0; } vh_sector = imagePlugin.ReadSectors(partition.Start + hfsp_offset, sectorsToRead); // Read volume header drSigWord = BigEndianBitConverter.ToUInt16(vh_sector, 0x400); if(drSigWord == HFSP_MAGIC || drSigWord == HFSX_MAGIC) return true; return false; } public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, Partition partition, out string information) { information = ""; ushort drSigWord; ushort xdrStABNt; ushort drAlBlSt; uint drAlBlkSiz; HFSPlusVolumeHeader HPVH = new HFSPlusVolumeHeader(); ulong hfsp_offset; bool wrapped; byte[] vh_sector; uint sectorsToRead = 0x800 / imagePlugin.ImageInfo.sectorSize; if(0x800 % imagePlugin.ImageInfo.sectorSize > 0) sectorsToRead++; vh_sector = imagePlugin.ReadSectors(partition.Start, sectorsToRead); // Read volume header, of HFS Wrapper MDB drSigWord = BigEndianBitConverter.ToUInt16(vh_sector, 0x400); // Check for HFS Wrapper MDB if(drSigWord == HFS_MAGIC) // "BD" { drSigWord = BigEndianBitConverter.ToUInt16(vh_sector, 0x47C); // Read embedded HFS+ signature if(drSigWord == HFSP_MAGIC) // "H+" { xdrStABNt = BigEndianBitConverter.ToUInt16(vh_sector, 0x47E); // Starting block number of embedded HFS+ volume drAlBlkSiz = BigEndianBitConverter.ToUInt32(vh_sector, 0x414); // Block size drAlBlSt = BigEndianBitConverter.ToUInt16(vh_sector, 0x41C); // Start of allocated blocks (in 512-byte/block) hfsp_offset = (ulong)(((drAlBlSt * 512) + (xdrStABNt * drAlBlkSiz)) / imagePlugin.GetSectorSize()); wrapped = true; } else { hfsp_offset = 0; wrapped = false; } } else { hfsp_offset = 0; wrapped = false; } vh_sector = imagePlugin.ReadSectors(partition.Start + hfsp_offset, sectorsToRead); // Read volume header HPVH.signature = BigEndianBitConverter.ToUInt16(vh_sector, 0x400); if(HPVH.signature == HFSP_MAGIC || HPVH.signature == HFSX_MAGIC) { StringBuilder sb = new StringBuilder(); if(HPVH.signature == 0x482B) sb.AppendLine("HFS+ filesystem."); if(HPVH.signature == 0x4858) sb.AppendLine("HFSX filesystem."); if(wrapped) sb.AppendLine("Volume is wrapped inside an HFS volume."); byte[] tmp = new byte[0x400]; Array.Copy(vh_sector, 0x400, tmp, 0, 0x400); vh_sector = tmp; HPVH = BigEndianMarshal.ByteArrayToStructureBigEndian(vh_sector); if(HPVH.version == 4 || HPVH.version == 5) { sb.AppendFormat("Filesystem version is {0}.", HPVH.version).AppendLine(); if((HPVH.attributes & 0x80) == 0x80) sb.AppendLine("Volume is locked on hardware."); if((HPVH.attributes & 0x100) == 0x100) sb.AppendLine("Volume is unmounted."); if((HPVH.attributes & 0x200) == 0x200) sb.AppendLine("There are bad blocks in the extents file."); if((HPVH.attributes & 0x400) == 0x400) sb.AppendLine("Volume does not require cache."); if((HPVH.attributes & 0x800) == 0x800) sb.AppendLine("Volume state is inconsistent."); if((HPVH.attributes & 0x1000) == 0x1000) sb.AppendLine("CNIDs are reused."); if((HPVH.attributes & 0x2000) == 0x2000) sb.AppendLine("Volume is journaled."); if((HPVH.attributes & 0x8000) == 0x8000) sb.AppendLine("Volume is locked on software."); sb.AppendFormat("Implementation that last mounted the volume: \"{0}\".", Encoding.ASCII.GetString(HPVH.lastMountedVersion)).AppendLine(); if((HPVH.attributes & 0x2000) == 0x2000) sb.AppendFormat("Journal starts at allocation block {0}.", HPVH.journalInfoBlock).AppendLine(); sb.AppendFormat("Creation date: {0}", DateHandlers.MacToDateTime(HPVH.createDate)).AppendLine(); sb.AppendFormat("Last modification date: {0}", DateHandlers.MacToDateTime(HPVH.modifyDate)).AppendLine(); if(HPVH.backupDate > 0) sb.AppendFormat("Last backup date: {0}", DateHandlers.MacToDateTime(HPVH.backupDate)).AppendLine(); else sb.AppendLine("Volume has never been backed up"); if(HPVH.backupDate > 0) sb.AppendFormat("Last check date: {0}", DateHandlers.MacToDateTime(HPVH.checkedDate)).AppendLine(); else sb.AppendLine("Volume has never been checked up"); sb.AppendFormat("{0} files on volume.", HPVH.fileCount).AppendLine(); sb.AppendFormat("{0} folders on volume.", HPVH.folderCount).AppendLine(); sb.AppendFormat("{0} bytes per allocation block.", HPVH.blockSize).AppendLine(); sb.AppendFormat("{0} allocation blocks.", HPVH.totalBlocks).AppendLine(); sb.AppendFormat("{0} free blocks.", HPVH.freeBlocks).AppendLine(); sb.AppendFormat("Next allocation block: {0}.", HPVH.nextAllocation).AppendLine(); sb.AppendFormat("Resource fork clump size: {0} bytes.", HPVH.rsrcClumpSize).AppendLine(); sb.AppendFormat("Data fork clump size: {0} bytes.", HPVH.dataClumpSize).AppendLine(); sb.AppendFormat("Next unused CNID: {0}.", HPVH.nextCatalogID).AppendLine(); sb.AppendFormat("Volume has been mounted writable {0} times.", HPVH.writeCount).AppendLine(); sb.AppendFormat("Allocation File is {0} bytes.", HPVH.allocationFile_logicalSize).AppendLine(); sb.AppendFormat("Extents File is {0} bytes.", HPVH.extentsFile_logicalSize).AppendLine(); sb.AppendFormat("Catalog File is {0} bytes.", HPVH.catalogFile_logicalSize).AppendLine(); sb.AppendFormat("Attributes File is {0} bytes.", HPVH.attributesFile_logicalSize).AppendLine(); sb.AppendFormat("Startup File is {0} bytes.", HPVH.startupFile_logicalSize).AppendLine(); sb.AppendLine("Finder info:"); sb.AppendFormat("CNID of bootable system's directory: {0}", HPVH.drFndrInfo0).AppendLine(); sb.AppendFormat("CNID of first-run application's directory: {0}", HPVH.drFndrInfo1).AppendLine(); sb.AppendFormat("CNID of previously opened directory: {0}", HPVH.drFndrInfo2).AppendLine(); sb.AppendFormat("CNID of bootable Mac OS 8 or 9 directory: {0}", HPVH.drFndrInfo3).AppendLine(); sb.AppendFormat("CNID of bootable Mac OS X directory: {0}", HPVH.drFndrInfo5).AppendLine(); if(HPVH.drFndrInfo6 != 0 && HPVH.drFndrInfo7 != 0) sb.AppendFormat("Mac OS X Volume ID: {0:X8}{1:X8}", HPVH.drFndrInfo6, HPVH.drFndrInfo7).AppendLine(); xmlFSType = new Schemas.FileSystemType(); if(HPVH.backupDate > 0) { xmlFSType.BackupDate = DateHandlers.MacToDateTime(HPVH.backupDate); xmlFSType.BackupDateSpecified = true; } xmlFSType.Bootable |= (HPVH.drFndrInfo0 != 0 || HPVH.drFndrInfo3 != 0 || HPVH.drFndrInfo5 != 0); xmlFSType.Clusters = HPVH.totalBlocks; xmlFSType.ClusterSize = (int)HPVH.blockSize; if(HPVH.createDate > 0) { xmlFSType.CreationDate = DateHandlers.MacToDateTime(HPVH.createDate); xmlFSType.CreationDateSpecified = true; } xmlFSType.Dirty = (HPVH.attributes & 0x100) != 0x100; xmlFSType.Files = HPVH.fileCount; xmlFSType.FilesSpecified = true; xmlFSType.FreeClusters = HPVH.freeBlocks; xmlFSType.FreeClustersSpecified = true; if(HPVH.modifyDate > 0) { xmlFSType.ModificationDate = DateHandlers.MacToDateTime(HPVH.modifyDate); xmlFSType.ModificationDateSpecified = true; } if(HPVH.signature == 0x482B) xmlFSType.Type = "HFS+"; if(HPVH.signature == 0x4858) xmlFSType.Type = "HFSX"; if(HPVH.drFndrInfo6 != 0 && HPVH.drFndrInfo7 != 0) xmlFSType.VolumeSerial = string.Format("{0:X8}{1:X8}", HPVH.drFndrInfo6, HPVH.drFndrInfo7); xmlFSType.SystemIdentifier = Encoding.ASCII.GetString(HPVH.lastMountedVersion); } else { sb.AppendFormat("Filesystem version is {0}.", HPVH.version).AppendLine(); sb.AppendLine("This version is not supported yet."); } information = sb.ToString(); } else return; } /// /// HFS+ Volume Header, should be at offset 0x0400 bytes in volume with a size of 532 bytes /// [StructLayout(LayoutKind.Sequential, Pack = 1)] struct HFSPlusVolumeHeader { /// 0x000, "H+" for HFS+, "HX" for HFSX public ushort signature; /// 0x002, 4 for HFS+, 5 for HFSX public ushort version; /// 0x004, Volume attributes public uint attributes; /// 0x008, Implementation that last mounted the volume. /// Reserved by Apple: /// "8.10" Mac OS 8.1 to 9.2.2 /// "10.0" Mac OS X /// "HFSJ" Journaled implementation /// "fsck" /sbin/fsck [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] lastMountedVersion; /// 0x00C, Allocation block number containing the journal public uint journalInfoBlock; /// 0x010, Date of volume creation public uint createDate; /// 0x014, Date of last volume modification public uint modifyDate; /// 0x018, Date of last backup public uint backupDate; /// 0x01C, Date of last consistency check public uint checkedDate; /// 0x020, File on the volume public uint fileCount; /// 0x024, Folders on the volume public uint folderCount; /// 0x028, Bytes per allocation block public uint blockSize; /// 0x02C, Allocation blocks on the volume public uint totalBlocks; /// 0x030, Free allocation blocks public uint freeBlocks; /// 0x034, Hint for next allocation block public uint nextAllocation; /// 0x038, Resource fork clump size public uint rsrcClumpSize; /// 0x03C, Data fork clump size public uint dataClumpSize; /// 0x040, Next unused CNID public uint nextCatalogID; /// 0x044, Times that the volume has been mounted writable public uint writeCount; /// 0x048, Used text encoding hints public ulong encodingsBitmap; /// 0x050, finderInfo[0], CNID for bootable system's directory public uint drFndrInfo0; /// 0x054, finderInfo[1], CNID of the directory containing the boot application public uint drFndrInfo1; /// 0x058, finderInfo[2], CNID of the directory that should be opened on boot public uint drFndrInfo2; /// 0x05C, finderInfo[3], CNID for Mac OS 8 or 9 directory public uint drFndrInfo3; /// 0x060, finderInfo[4], Reserved public uint drFndrInfo4; /// 0x064, finderInfo[5], CNID for Mac OS X directory public uint drFndrInfo5; /// 0x068, finderInfo[6], first part of Mac OS X volume ID public uint drFndrInfo6; /// 0x06C, finderInfo[7], second part of Mac OS X volume ID public uint drFndrInfo7; // HFSPlusForkData allocationFile; /// 0x070 public ulong allocationFile_logicalSize; /// 0x078 public uint allocationFile_clumpSize; /// 0x07C public uint allocationFile_totalBlocks; /// 0x080 public uint allocationFile_extents_startBlock0; /// 0x084 public uint allocationFile_extents_blockCount0; /// 0x088 public uint allocationFile_extents_startBlock1; /// 0x08C public uint allocationFile_extents_blockCount1; /// 0x090 public uint allocationFile_extents_startBlock2; /// 0x094 public uint allocationFile_extents_blockCount2; /// 0x098 public uint allocationFile_extents_startBlock3; /// 0x09C public uint allocationFile_extents_blockCount3; /// 0x0A0 public uint allocationFile_extents_startBlock4; /// 0x0A4 public uint allocationFile_extents_blockCount4; /// 0x0A8 public uint allocationFile_extents_startBlock5; /// 0x0AC public uint allocationFile_extents_blockCount5; /// 0x0B0 public uint allocationFile_extents_startBlock6; /// 0x0B4 public uint allocationFile_extents_blockCount6; /// 0x0B8 public uint allocationFile_extents_startBlock7; /// 0x0BC public uint allocationFile_extents_blockCount7; // HFSPlusForkData extentsFile; /// 0x0C0 public ulong extentsFile_logicalSize; /// 0x0C8 public uint extentsFile_clumpSize; /// 0x0CC public uint extentsFile_totalBlocks; /// 0x0D0 public uint extentsFile_extents_startBlock0; /// 0x0D4 public uint extentsFile_extents_blockCount0; /// 0x0D8 public uint extentsFile_extents_startBlock1; /// 0x0DC public uint extentsFile_extents_blockCount1; /// 0x0E0 public uint extentsFile_extents_startBlock2; /// 0x0E4 public uint extentsFile_extents_blockCount2; /// 0x0E8 public uint extentsFile_extents_startBlock3; /// 0x0EC public uint extentsFile_extents_blockCount3; /// 0x0F0 public uint extentsFile_extents_startBlock4; /// 0x0F4 public uint extentsFile_extents_blockCount4; /// 0x0F8 public uint extentsFile_extents_startBlock5; /// 0x0FC public uint extentsFile_extents_blockCount5; /// 0x100 public uint extentsFile_extents_startBlock6; /// 0x104 public uint extentsFile_extents_blockCount6; /// 0x108 public uint extentsFile_extents_startBlock7; /// 0x10C public uint extentsFile_extents_blockCount7; // HFSPlusForkData catalogFile; /// 0x110 public ulong catalogFile_logicalSize; /// 0x118 public uint catalogFile_clumpSize; /// 0x11C public uint catalogFile_totalBlocks; /// 0x120 public uint catalogFile_extents_startBlock0; /// 0x124 public uint catalogFile_extents_blockCount0; /// 0x128 public uint catalogFile_extents_startBlock1; /// 0x12C public uint catalogFile_extents_blockCount1; /// 0x130 public uint catalogFile_extents_startBlock2; /// 0x134 public uint catalogFile_extents_blockCount2; /// 0x138 public uint catalogFile_extents_startBlock3; /// 0x13C public uint catalogFile_extents_blockCount3; /// 0x140 public uint catalogFile_extents_startBlock4; /// 0x144 public uint catalogFile_extents_blockCount4; /// 0x148 public uint catalogFile_extents_startBlock5; /// 0x14C public uint catalogFile_extents_blockCount5; /// 0x150 public uint catalogFile_extents_startBlock6; /// 0x154 public uint catalogFile_extents_blockCount6; /// 0x158 public uint catalogFile_extents_startBlock7; /// 0x15C public uint catalogFile_extents_blockCount7; // HFSPlusForkData attributesFile; /// 0x160 public ulong attributesFile_logicalSize; /// 0x168 public uint attributesFile_clumpSize; /// 0x16C public uint attributesFile_totalBlocks; /// 0x170 public uint attributesFile_extents_startBlock0; /// 0x174 public uint attributesFile_extents_blockCount0; /// 0x178 public uint attributesFile_extents_startBlock1; /// 0x17C public uint attributesFile_extents_blockCount1; /// 0x180 public uint attributesFile_extents_startBlock2; /// 0x184 public uint attributesFile_extents_blockCount2; /// 0x188 public uint attributesFile_extents_startBlock3; /// 0x18C public uint attributesFile_extents_blockCount3; /// 0x190 public uint attributesFile_extents_startBlock4; /// 0x194 public uint attributesFile_extents_blockCount4; /// 0x198 public uint attributesFile_extents_startBlock5; /// 0x19C public uint attributesFile_extents_blockCount5; /// 0x1A0 public uint attributesFile_extents_startBlock6; /// 0x1A4 public uint attributesFile_extents_blockCount6; /// 0x1A8 public uint attributesFile_extents_startBlock7; /// 0x1AC public uint attributesFile_extents_blockCount7; // HFSPlusForkData startupFile; /// 0x1B0 public ulong startupFile_logicalSize; /// 0x1B8 public uint startupFile_clumpSize; /// 0x1BC public uint startupFile_totalBlocks; /// 0x1C0 public uint startupFile_extents_startBlock0; /// 0x1C4 public uint startupFile_extents_blockCount0; /// 0x1C8 public uint startupFile_extents_startBlock1; /// 0x1D0 public uint startupFile_extents_blockCount1; /// 0x1D4 public uint startupFile_extents_startBlock2; /// 0x1D8 public uint startupFile_extents_blockCount2; /// 0x1DC public uint startupFile_extents_startBlock3; /// 0x1E0 public uint startupFile_extents_blockCount3; /// 0x1E4 public uint startupFile_extents_startBlock4; /// 0x1E8 public uint startupFile_extents_blockCount4; /// 0x1EC public uint startupFile_extents_startBlock5; /// 0x1F0 public uint startupFile_extents_blockCount5; /// 0x1F4 public uint startupFile_extents_startBlock6; /// 0x1F8 public uint startupFile_extents_blockCount6; /// 0x1FC public uint startupFile_extents_startBlock7; /// 0x200 public uint startupFile_extents_blockCount7; } public override Errno Mount() { return Errno.NotImplemented; } public override Errno Mount(bool debug) { return Errno.NotImplemented; } public override Errno Unmount() { return Errno.NotImplemented; } public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock) { return Errno.NotImplemented; } public override Errno GetAttributes(string path, ref FileAttributes attributes) { return Errno.NotImplemented; } public override Errno ListXAttr(string path, ref List xattrs) { return Errno.NotImplemented; } public override Errno GetXattr(string path, string xattr, ref byte[] buf) { return Errno.NotImplemented; } public override Errno Read(string path, long offset, long size, ref byte[] buf) { return Errno.NotImplemented; } public override Errno ReadDir(string path, ref List contents) { return Errno.NotImplemented; } public override Errno StatFs(ref FileSystemInfo stat) { return Errno.NotImplemented; } public override Errno Stat(string path, ref FileEntryInfo stat) { return Errno.NotImplemented; } public override Errno ReadLink(string path, ref string dest) { return Errno.NotImplemented; } } }