import struct

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

def read_virt(virt_offset, length):
    result = bytearray(length)
    pos = virt_offset
    remaining = length
    with open('/dev/nbd0', 'rb') as f:
        while remaining > 0:
            f.seek(pos)
            chunk = f.read(min(remaining, 65536))
            if not chunk: break
            dst = pos - virt_offset
            result[dst:dst+len(chunk)] = chunk
            pos += len(chunk)
            remaining -= len(chunk)
    return bytes(result)

def get_inode_table_block(group):
    # Use backup GDT at group 1
    gdt_start = (1 * 32768 + 1) * BSIZE
    entry = read_virt(gdt_start + group * 64, 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

def read_inode(inode_num):
    group = (inode_num - 1) // IPG
    index = (inode_num - 1) % IPG
    it_block = get_inode_table_block(group)
    inode_off = it_block * BSIZE + index * INODE_SIZE
    return read_virt(inode_off, INODE_SIZE)

def read_extents(inode_data):
    blocks = []
    eh_magic   = struct.unpack_from('<H', inode_data, 40)[0]
    if eh_magic != 0xf30a:
        return blocks
    eh_entries = struct.unpack_from('<H', inode_data, 42)[0]
    eh_depth   = struct.unpack_from('<H', inode_data, 46)[0]
    if eh_depth == 0:
        for i in range(min(eh_entries, 4)):
            off = 52 + i * 12
            ee_len     = struct.unpack_from('<H', inode_data, off+4)[0]
            ee_start_hi = struct.unpack_from('<H', inode_data, off+6)[0]
            ee_start_lo = struct.unpack_from('<I', inode_data, off+8)[0]
            ee_start = (ee_start_hi << 32) | ee_start_lo
            for b in range(ee_len):
                blocks.append(ee_start + b)
    return blocks

def list_dir(inode_num):
    inode_data = read_inode(inode_num)
    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]
    print(f'Inode {inode_num}: mode=0x{mode:04x} size={size} links={links}')

    entries = []
    for block_num in read_extents(inode_data):
        data = read_virt(block_num * BSIZE, BSIZE)
        off = 0
        while off < BSIZE - 8:
            ino     = struct.unpack_from('<I', data, off)[0]
            rec_len = struct.unpack_from('<H', data, off+4)[0]
            name_len = data[off+6]
            ftype   = data[off+7]
            if rec_len < 8: break
            if ino > 0 and name_len > 0:
                name = data[off+8:off+8+name_len].decode('utf-8',errors='replace')
                grp  = (ino-1)//IPG
                entries.append((name, ino, ftype, grp))
            off += rec_len

    type_names = {1:'file',2:'dir',7:'symlink'}
    print(f'Directory entries ({len(entries)}):')
    for name, ino, ftype, grp in sorted(entries):
        status = 'INTACT' if grp >= 13 else 'LOST'
        tname  = type_names.get(ftype, str(ftype))
        print(f'  [{status}] {tname:6s} inode={ino:10d} group={grp:6d} {name!r}')

# Read the volumes directory
list_dir(1585918)
