import struct

CHUNK    = 128*512
LV_START = 5120000*512
BSIZE    = 4096
IPG      = 8192

def read_virt(virt_byte, length):
    result = bytearray(length)
    pos = virt_byte
    remaining = length
    with open('/dev/md0','rb') as f:
        while remaining > 0:
            group     = pos // (5*CHUNK)
            in_group  = pos % (5*CHUNK)
            chunk_idx = in_group // CHUNK
            intra     = in_group % CHUNK
            seg_len   = min(CHUNK-intra, remaining)
            dst_off   = pos - virt_byte
            if chunk_idx != 4:
                phys = LV_START + group*4*CHUNK + chunk_idx*CHUNK + intra
                f.seek(phys)
                data = f.read(seg_len)
                result[dst_off:dst_off+len(data)] = data
            pos       += seg_len
            remaining -= seg_len
    return bytes(result)

NUM_GROUPS = 35728
gdt_data   = read_virt(BSIZE, NUM_GROUPS*64)

def get_inode_table_block(group):
    entry = gdt_data[group*64:(group+1)*64]
    it_lo = struct.unpack_from('<I', entry,  8)[0]
    it_hi = struct.unpack_from('<I', entry, 40)[0]
    return (it_hi<<32)|it_lo

# Read /var inode raw and dump everything
var_inode = 1310721
group    = (var_inode-1)//IPG
idx      = (var_inode-1)%IPG
it_block = get_inode_table_block(group)
it_virt  = it_block*BSIZE + idx*256

print(f'/var inode {var_inode}:')
print(f'  group={group} idx={idx}')
print(f'  it_block={it_block}')
print(f'  inode virt byte={it_virt}')

# Check if this virtual byte is in a metadata chunk
in_group  = it_virt % (5*CHUNK)
chunk_idx = in_group // CHUNK
print(f'  chunk_idx={chunk_idx} (4=metadata, returns zeros)')

inode_data = read_virt(it_virt, 256)
print(f'  raw inode: {inode_data[:64].hex()}')

mode      = struct.unpack_from('<H', inode_data,  0)[0]
size      = struct.unpack_from('<I', inode_data,  4)[0]
links     = struct.unpack_from('<H', inode_data, 26)[0]
mtime     = struct.unpack_from('<I', inode_data, 16)[0]
ext_magic = struct.unpack_from('<H', inode_data, 40)[0]
eh_entries= struct.unpack_from('<H', inode_data, 42)[0]
eh_depth  = struct.unpack_from('<H', inode_data, 46)[0]

print(f'  mode=0x{mode:04x} size={size} links={links}')
print(f'  mtime={mtime} ext_magic=0x{ext_magic:04x}')
print(f'  eh_entries={eh_entries} eh_depth={eh_depth}')
print()

# Dump extent tree
if ext_magic == 0xf30a:
    print('Extent tree:')
    if eh_depth == 0:
        for i in range(min(eh_entries,4)):
            off      = 52+i*12
            ee_block = struct.unpack_from('<I', inode_data, off)[0]
            ee_len   = struct.unpack_from('<H', inode_data, off+4)[0]
            ee_hi    = struct.unpack_from('<H', inode_data, off+6)[0]
            ee_lo    = struct.unpack_from('<I', inode_data, off+8)[0]
            ee_start = (ee_hi<<32)|ee_lo
            print(f'  Extent {i}: logical={ee_block} len={ee_len} '
                  f'phys_start={ee_start}')
            
            # Check if this block is in a metadata chunk
            blk_virt  = ee_start*BSIZE
            in_group  = blk_virt % (5*CHUNK)
            chunk_idx = in_group // CHUNK
            print(f'    virt_byte={blk_virt} chunk_idx={chunk_idx}')
            
            # Read the block
            blk_data = read_virt(blk_virt, BSIZE)
            nonzero  = sum(1 for b in blk_data if b != 0)
            print(f'    nonzero bytes: {nonzero}/4096')
            print(f'    first 32 bytes: {blk_data[:32].hex()}')
            
            # Try to parse as directory
            off2 = 0
            count = 0
            while off2 < BSIZE-8:
                ino     = struct.unpack_from('<I', blk_data, off2)[0]
                rec_len = struct.unpack_from('<H', blk_data, off2+4)[0]
                name_len= blk_data[off2+6]
                ftype   = blk_data[off2+7]
                if rec_len < 8: break
                if ino > 0 and name_len > 0:
                    name = blk_data[off2+8:off2+8+name_len].decode('utf-8',errors='replace')
                    tname = {1:'file',2:'dir',7:'link'}.get(ftype,'?')
                    print(f'    entry: {tname} inode={ino} {name!r}')
                    count += 1
                off2 += rec_len
            if count == 0:
                print(f'    No valid directory entries found')
    else:
        print(f'  depth={eh_depth} - need to follow interior nodes')
        # Read interior node
        for i in range(min(eh_entries,4)):
            off    = 52+i*12
            ei_block = struct.unpack_from('<I', inode_data, off)[0]
            ei_hi    = struct.unpack_from('<H', inode_data, off+4)[0] 
            ei_lo    = struct.unpack_from('<I', inode_data, off+8)[0]  # wrong offsets for idx
            # Extent index: ee_block(4) + ei_leaf_lo(4) + ei_leaf_hi(2) + unused(2)
            ei_leaf_lo = struct.unpack_from('<I', inode_data, off+4)[0]
            ei_leaf_hi = struct.unpack_from('<H', inode_data, off+8)[0]
            ei_leaf    = (ei_leaf_hi<<32)|ei_leaf_lo
            print(f'  Index {i}: logical={ei_block} leaf={ei_leaf}')
            
            # Read the leaf block
            leaf_virt = ei_leaf*BSIZE
            leaf_data = read_virt(leaf_virt, BSIZE)
            leaf_magic = struct.unpack_from('<H', leaf_data, 0)[0]
            leaf_entries = struct.unpack_from('<H', leaf_data, 2)[0]
            leaf_depth = struct.unpack_from('<H', leaf_data, 6)[0]
            print(f'    leaf magic=0x{leaf_magic:04x} entries={leaf_entries} depth={leaf_depth}')
            
            if leaf_magic == 0xf30a and leaf_depth == 0:
                for j in range(min(leaf_entries,4)):
                    off2     = 12+j*12
                    ee_len   = struct.unpack_from('<H', leaf_data, off2+4)[0]
                    ee_hi    = struct.unpack_from('<H', leaf_data, off2+6)[0]
                    ee_lo    = struct.unpack_from('<I', leaf_data, off2+8)[0]
                    ee_start = (ee_hi<<32)|ee_lo
                    print(f'    Extent {j}: len={ee_len} start={ee_start}')
                    
                    blk_data = read_virt(ee_start*BSIZE, BSIZE)
                    off3 = 0
                    while off3 < BSIZE-8:
                        ino     = struct.unpack_from('<I', blk_data, off3)[0]
                        rec_len = struct.unpack_from('<H', blk_data, off3+4)[0]
                        name_len= blk_data[off3+6]
                        ftype   = blk_data[off3+7]
                        if rec_len < 8: break
                        if ino > 0 and name_len > 0:
                            name = blk_data[off3+8:off3+8+name_len].decode('utf-8',errors='replace')
                            tname = {1:'file',2:'dir',7:'link'}.get(ftype,'?')
                            print(f'      entry: {tname} inode={ino} {name!r}')
                        off3 += rec_len
