diff --git a/Aaru.Filesystems/JFS/Consts.cs b/Aaru.Filesystems/JFS/Consts.cs index e583aa43a..e4971c119 100644 --- a/Aaru.Filesystems/JFS/Consts.cs +++ b/Aaru.Filesystems/JFS/Consts.cs @@ -128,4 +128,35 @@ public sealed partial class JFS /// Log page size in bytes const int LOGPSIZE = 4096; + + // DataExtent (dxd_t) flags + /// DXD flag: B+-tree index + const byte DXD_INDEX = 0x80; + + /// DXD flag: in-line data extent + const byte DXD_INLINE = 0x40; + + /// DXD flag: out-of-line single extent + const byte DXD_EXTENT = 0x20; + + /// DXD flag: out-of-line file (inode) + const byte DXD_FILE = 0x10; + + /// DXD flag: inconsistency detected + const byte DXD_CORRUPT = 0x08; + + /// Extended mode bit: inline EA area free + const uint INLINEEA = 0x00040000; + + /// + /// Offset of inline EA data within inode extension area (di_u). + /// Layout: _data[96] + unused[16] + dxd[16] + _fastsymlink/rdev[128] + _inlineea[128] + /// + const int INLINEEA_OFFSET = 256; + + /// Size of inline EA area in bytes + const int INLINEEA_SIZE = 128; + + /// Prefix for unknown-namespace OS/2 attributes + const string XATTR_OS2_PREFIX = "os2."; } \ No newline at end of file diff --git a/Aaru.Filesystems/JFS/DTree.cs b/Aaru.Filesystems/JFS/DTree.cs index 3075c7712..1449366b0 100644 --- a/Aaru.Filesystems/JFS/DTree.cs +++ b/Aaru.Filesystems/JFS/DTree.cs @@ -48,8 +48,7 @@ public sealed partial class JFS // dtroot starts at offset 96 in di_u (after dir_table_slot[12]) const int dtOffset = 96; - // Parse dtroot header (32 bytes = 1 slot) - // DASD(16) + flag(1) + nextindex(1) + freecnt(1) + freelist(1) + idotdot(4) + stbl(8) + // ...existing code for header parsing... byte dtrootFlag = extensionData[dtOffset + 16]; byte dtrootNextindex = extensionData[dtOffset + 17]; @@ -62,17 +61,14 @@ public sealed partial class JFS return ErrorNumber.NoError; } - // Check if this is a leaf root (should always be for inline dtree) if((dtrootFlag & BT_LEAF) == 0) { - // The root dtree is internal - entries are in external pages AaruLogging.Debug(MODULE_NAME, "Root dtree is internal, reading external pages..."); return ParseDtreeInternalRoot(extensionData, dtOffset, dtrootNextindex, out entries); } - // Read the sorted entry index table - // stbl is at dtOffset + 24 (after DASD(16) + flag(1) + nextindex(1) + freecnt(1) + freelist(1) + idotdot(4)) + // Leaf root - read entries inline int stblOffset = dtOffset + 24; bool hasIndex = _superblock.s_flags.HasFlag(Flags.DirIndex); @@ -84,31 +80,25 @@ public sealed partial class JFS if(slotIdx < 0 || slotIdx >= DTROOTMAXSLOT) continue; - // The slot is at dtOffset + slotIdx * DTSLOTSIZE int slotOffset = dtOffset + slotIdx * DTSLOTSIZE; - // Parse ldtentry: inumber(4) + next(1) + namlen(1) + name(22 or 26) [+ index(4)] var inumber = BitConverter.ToUInt32(extensionData, slotOffset); var next = (sbyte)extensionData[slotOffset + 4]; byte namlen = extensionData[slotOffset + 5]; - // Read name characters from the first segment int charsInHead = Math.Min(namlen, headDataLen); var nameChars = new char[namlen]; for(var c = 0; c < charsInHead; c++) nameChars[c] = (char)BitConverter.ToUInt16(extensionData, slotOffset + 6 + c * 2); - // Follow continuation slots for remaining characters int charsCopied = charsInHead; while(next >= 0 && charsCopied < namlen && next < DTROOTMAXSLOT) { int contSlotOffset = dtOffset + next * DTSLOTSIZE; - - // dtslot: next(1) + cnt(1) + name[15](30) - var contNext = (sbyte)extensionData[contSlotOffset]; - int cnt = Math.Min(namlen - charsCopied, 15); + var contNext = (sbyte)extensionData[contSlotOffset]; + int cnt = Math.Min(namlen - charsCopied, 15); for(var c = 0; c < cnt && charsCopied < namlen; c++, charsCopied++) nameChars[charsCopied] = (char)BitConverter.ToUInt16(extensionData, contSlotOffset + 2 + c * 2); @@ -132,9 +122,10 @@ public sealed partial class JFS { entries = new Dictionary(StringComparer.Ordinal); + var visited = new HashSet(); + int stblOffset = dtOffset + 24; - // Each stbl entry in an internal root points to an idtentry which has a child extent for(var i = 0; i < nextindex && i < 8; i++) { int slotIdx = (sbyte)extensionData[stblOffset + i]; @@ -143,24 +134,28 @@ public sealed partial class JFS int slotOffset = dtOffset + slotIdx * DTSLOTSIZE; - // idtentry: xd(8) + next(1) + namlen(1) + name(22) - // The xd (pxd_t) at the start gives the child page address - var lenAddr = BitConverter.ToUInt32(extensionData, slotOffset); - var addr2 = BitConverter.ToUInt32(extensionData, slotOffset + 4); - ulong n = lenAddr & ~0xFFFFFFu; - var childAddr = (long)((n << 8) + addr2); + // idtentry: xd (pxd_t, 8 bytes) at the start — extract address using pxd formula + long childAddr = PxdAddress(BitConverter.ToUInt32(extensionData, slotOffset), + BitConverter.ToUInt32(extensionData, slotOffset + 4)); if(childAddr <= 0) continue; + if(!visited.Add(childAddr)) + { + AaruLogging.Debug(MODULE_NAME, + "DTree internal root: skipping already-visited block {0}", + childAddr); + + continue; + } + AaruLogging.Debug(MODULE_NAME, "DTree internal root: following child page at block {0}", childAddr); - // Read the child dtree page ErrorNumber errno = ReadBytes(childAddr * _superblock.s_bsize, PSIZE, out byte[] childPage); if(errno != ErrorNumber.NoError) continue; - // Parse child page recursively - errno = ParseDtreePage(childPage, entries); + errno = ParseDtreePage(childPage, entries, visited); if(errno != ErrorNumber.NoError) { @@ -172,10 +167,8 @@ public sealed partial class JFS } /// Parses a dtree page (leaf or internal) and collects directory entries - ErrorNumber ParseDtreePage(byte[] pageData, Dictionary entries) + ErrorNumber ParseDtreePage(byte[] pageData, Dictionary entries, HashSet visited) { - // dtpage header: next(8) + prev(8) + flag(1) + nextindex(1) + freecnt(1) + freelist(1) - // + maxslot(1) + stblindex(1) + rsrvd(2) + self(8) = 32 bytes byte flag = pageData[16]; byte nextindex = pageData[17]; byte maxslot = pageData[20]; @@ -190,79 +183,13 @@ public sealed partial class JFS if((flag & BT_LEAF) != 0) { - // Leaf page - extract entries - // stbl is at slot[stblindex] - int stblByteOffset = stblindex * DTSLOTSIZE; - - bool hasIndex = _superblock.s_flags.HasFlag(Flags.DirIndex); - int headDataLen = hasIndex ? DTLHDRDATALEN : DTLHDRDATALEN_LEGACY; - - for(var i = 0; i < nextindex; i++) - { - if(stblByteOffset + i >= pageData.Length) break; - - int slotIdx = (sbyte)pageData[stblByteOffset + i]; - - if(slotIdx < 0 || slotIdx >= maxslot) continue; - - int slotOffset = slotIdx * DTSLOTSIZE; - - if(slotOffset + 6 > pageData.Length) continue; - - var inumber = BitConverter.ToUInt32(pageData, slotOffset); - var next = (sbyte)pageData[slotOffset + 4]; - byte namlen = pageData[slotOffset + 5]; - - if(namlen == 0 || inumber == 0) continue; - - int charsInHead = Math.Min(namlen, headDataLen); - var nameChars = new char[namlen]; - - for(var c = 0; c < charsInHead && slotOffset + 6 + c * 2 + 1 < pageData.Length; c++) - nameChars[c] = (char)BitConverter.ToUInt16(pageData, slotOffset + 6 + c * 2); - - int charsCopied = charsInHead; - - while(next >= 0 && charsCopied < namlen && next < maxslot) - { - int contSlotOffset = next * DTSLOTSIZE; - - if(contSlotOffset + 2 >= pageData.Length) break; - - var contNext = (sbyte)pageData[contSlotOffset]; - int cnt = Math.Min(namlen - charsCopied, 15); - - for(var c = 0; c < cnt && charsCopied < namlen; c++, charsCopied++) - { - if(contSlotOffset + 2 + c * 2 + 1 >= pageData.Length) break; - - nameChars[charsCopied] = (char)BitConverter.ToUInt16(pageData, contSlotOffset + 2 + c * 2); - } - - next = contNext; - } - - var name = new string(nameChars, 0, Math.Min(charsCopied, namlen)); - - if(!string.IsNullOrEmpty(name)) entries[name] = inumber; - } - - // Follow next sibling page if present - var nextPage = BitConverter.ToUInt64(pageData, 0); - - if(nextPage != 0) - { - ErrorNumber errno = ReadBytes((long)nextPage * _superblock.s_bsize, PSIZE, out byte[] nextPageData); - - if(errno == ErrorNumber.NoError) ParseDtreePage(nextPageData, entries); - } + ParseDtreeLeafPage(pageData, nextindex, maxslot, stblindex, entries); return ErrorNumber.NoError; } if((flag & BT_INTERNAL) != 0) { - // Internal page - follow child pointers int stblByteOffset = stblindex * DTSLOTSIZE; for(var i = 0; i < nextindex; i++) @@ -277,17 +204,26 @@ public sealed partial class JFS if(slotOffset + 8 > pageData.Length) continue; - // idtentry: xd(8) + ... - var lenAddr = BitConverter.ToUInt32(pageData, slotOffset); - var addr2 = BitConverter.ToUInt32(pageData, slotOffset + 4); - ulong addrN = lenAddr & ~0xFFFFFFu; - var childAddr = (long)((addrN << 8) + addr2); + // idtentry: xd (pxd_t, 8 bytes) at the start — extract address using pxd formula + long childAddr = PxdAddress(BitConverter.ToUInt32(pageData, slotOffset), + BitConverter.ToUInt32(pageData, slotOffset + 4)); if(childAddr <= 0) continue; + if(!visited.Add(childAddr)) + { + AaruLogging.Debug(MODULE_NAME, + "DTree page: skipping already-visited block {0}", + childAddr); + + continue; + } + + AaruLogging.Debug(MODULE_NAME, "DTree page: following child at block {0}", childAddr); + ErrorNumber errno = ReadBytes(childAddr * _superblock.s_bsize, PSIZE, out byte[] childPage); - if(errno == ErrorNumber.NoError) ParseDtreePage(childPage, entries); + if(errno == ErrorNumber.NoError) ParseDtreePage(childPage, entries, visited); } return ErrorNumber.NoError; @@ -297,4 +233,65 @@ public sealed partial class JFS return ErrorNumber.InvalidArgument; } + + /// Extracts directory entries from a single dtree leaf page + void ParseDtreeLeafPage(byte[] pageData, byte nextindex, byte maxslot, byte stblindex, + Dictionary entries) + { + // ...existing code stays the same... + int stblByteOffset = stblindex * DTSLOTSIZE; + + bool hasIndex = _superblock.s_flags.HasFlag(Flags.DirIndex); + int headDataLen = hasIndex ? DTLHDRDATALEN : DTLHDRDATALEN_LEGACY; + + for(var i = 0; i < nextindex; i++) + { + if(stblByteOffset + i >= pageData.Length) break; + + int slotIdx = (sbyte)pageData[stblByteOffset + i]; + + if(slotIdx < 0 || slotIdx >= maxslot) continue; + + int slotOffset = slotIdx * DTSLOTSIZE; + + if(slotOffset + 6 > pageData.Length) continue; + + var inumber = BitConverter.ToUInt32(pageData, slotOffset); + var next = (sbyte)pageData[slotOffset + 4]; + byte namlen = pageData[slotOffset + 5]; + + if(namlen == 0 || inumber == 0) continue; + + int charsInHead = Math.Min(namlen, headDataLen); + var nameChars = new char[namlen]; + + for(var c = 0; c < charsInHead && slotOffset + 6 + c * 2 + 1 < pageData.Length; c++) + nameChars[c] = (char)BitConverter.ToUInt16(pageData, slotOffset + 6 + c * 2); + + int charsCopied = charsInHead; + + while(next >= 0 && charsCopied < namlen && next < maxslot) + { + int contSlotOffset = next * DTSLOTSIZE; + + if(contSlotOffset + 2 >= pageData.Length) break; + + var contNext = (sbyte)pageData[contSlotOffset]; + int cnt = Math.Min(namlen - charsCopied, 15); + + for(var c = 0; c < cnt && charsCopied < namlen; c++, charsCopied++) + { + if(contSlotOffset + 2 + c * 2 + 1 >= pageData.Length) break; + + nameChars[charsCopied] = (char)BitConverter.ToUInt16(pageData, contSlotOffset + 2 + c * 2); + } + + next = contNext; + } + + var name = new string(nameChars, 0, Math.Min(charsCopied, namlen)); + + if(!string.IsNullOrEmpty(name)) entries[name] = inumber; + } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/JFS/Extent.cs b/Aaru.Filesystems/JFS/Extent.cs index d30d525b6..425fc37aa 100644 --- a/Aaru.Filesystems/JFS/Extent.cs +++ b/Aaru.Filesystems/JFS/Extent.cs @@ -42,4 +42,15 @@ public sealed partial class JFS /// Extracts the 24-bit length from a physical extent descriptor (pxd_t) static uint ExtentLength(Extent pxd) => pxd.len_addr & 0xFFFFFF; + + /// Extracts the 40-bit address from raw pxd_t fields + /// The len_addr field (lower 24 bits = length, upper 8 bits = high address byte) + /// The addr2 field (lower 32 bits of address) + /// The 40-bit block address + static long PxdAddress(uint lenAddr, uint addr2) + { + ulong n = lenAddr & ~0xFFFFFFu; + + return (long)((n << 8) + addr2); + } } \ No newline at end of file