import struct

CHUNK     = 128*512
LV_START  = 5120000*512
BSIZE     = 4096
BPG       = 32768
GDT_ENTRY = 64
NUM_GROUPS = 35728

def is_meta(virt_byte):
    in_group  = virt_byte % (5*CHUNK)
    return (in_group // CHUNK) == 4

def raw_read(virt_offset, length):
    result = bytearray(length)
    pos = virt_offset
    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_offset
            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)

# Build merged GDT: for each group, use whichever of primary/backup
# is NOT in a metadata chunk
print('Building merged GDT...')
primary_start = BSIZE                    # block 1
backup_start  = (1*BPG + 1) * BSIZE     # group 1 backup

# Read both GDTs in full
primary_gdt = raw_read(primary_start, NUM_GROUPS * GDT_ENTRY)
backup_gdt  = raw_read(backup_start,  NUM_GROUPS * GDT_ENTRY)

merged = bytearray(NUM_GROUPS * GDT_ENTRY)
primary_used = 0
backup_used  = 0
neither      = 0

for g in range(NUM_GROUPS):
    prim_byte   = primary_start + g * GDT_ENTRY
    backup_byte = backup_start  + g * GDT_ENTRY
    src_off     = g * GDT_ENTRY

    if not is_meta(prim_byte):
        # Primary is valid - use it
        merged[src_off:src_off+GDT_ENTRY] = primary_gdt[src_off:src_off+GDT_ENTRY]
        primary_used += 1
    elif not is_meta(backup_byte):
        # Primary is in metadata chunk - use backup
        merged[src_off:src_off+GDT_ENTRY] = backup_gdt[src_off:src_off+GDT_ENTRY]
        backup_used += 1
    else:
        # Both bad - shouldn't happen given our analysis
        neither += 1

print(f'From primary GDT: {primary_used}')
print(f'From backup GDT:  {backup_used}')
print(f'Neither (error):  {neither}')
assert neither == 0, 'Unexpected gap in coverage!'

# Verify merged GDT looks sane
print()
print('Sample entries:')
for g in [0, 1, 100, 1000, 32699, 35000, 35727]:
    e = merged[g*GDT_ENTRY:(g+1)*GDT_ENTRY]
    bb = struct.unpack_from('<I',e,0)[0]
    ib = struct.unpack_from('<I',e,4)[0]
    it = struct.unpack_from('<I',e,8)[0]
    cs = struct.unpack_from('<H',e,30)[0]
    print(f'  Group {g:6d}: bb={bb:8d} ib={ib:8d} it={it:10d} csum=0x{cs:04x}')

with open('/tmp/merged_gdt.bin','wb') as f:
    f.write(merged)
print(f'Saved /tmp/merged_gdt.bin ({len(merged)//1024}KB)')
