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)

# Read GDT from md0
print('Reading GDT from /dev/md0...')
gdt_data = read_virt(BSIZE, 20*64)

for g in range(20):
    entry    = gdt_data[g*64:(g+1)*64]
    it_lo    = struct.unpack_from('<I', entry,  8)[0]
    it_hi    = struct.unpack_from('<I', entry, 40)[0]
    it_block = (it_hi<<32)|it_lo
    flags    = struct.unpack_from('<H', entry, 18)[0]
    print(f'  Group {g:3d}: it_block={it_block:10d} '
          f'formula={1070+g*512} '
          f'match={it_block==1070+g*512} '
          f'flags=0x{flags:04x}')

# Read /var inode
var_inode = 1310721
var_group = (var_inode-1)//IPG
var_idx   = (var_inode-1)%IPG
print(f'\n/var inode {var_inode}: group={var_group} idx={var_idx}')

gdt_entry = read_virt(BSIZE + var_group*64, 64)
it_lo    = struct.unpack_from('<I', gdt_entry,  8)[0]
it_hi    = struct.unpack_from('<I', gdt_entry, 40)[0]
it_block = (it_hi<<32)|it_lo
print(f'GDT it_block: {it_block}')

inode_virt = it_block*BSIZE + var_idx*256
inode_data = read_virt(inode_virt, 256)
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]
print(f'mode=0x{mode:04x} size={size} links={links} '
      f'mtime={mtime} ext_magic=0x{ext_magic:04x}')
print(f'Valid dir: {(mode&0xf000)==0x4000 and ext_magic==0xf30a}')

# If valid, list directory contents
if (mode&0xf000)==0x4000 and ext_magic==0xf30a:
    print()
    print('Directory contents:')
    entries_raw = struct.unpack_from('<H', inode_data, 42)[0]
    depth       = struct.unpack_from('<H', inode_data, 46)[0]
    if depth == 0:
        for i in range(min(entries_raw,4)):
            off    = 52+i*12
            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]
            blk    = (ee_hi<<32)|ee_lo
            blk_data = read_virt(blk*BSIZE, BSIZE)
            off2 = 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')
                    if name not in ('.','..'): 
                        tname = {1:'file',2:'dir',7:'link'}.get(ftype,'?')
                        print(f'  {tname:4s} inode={ino:10d} {name!r}')
                off2 += rec_len
