#!/usr/bin/env python3
"""DF-3083 craft (host): build a small clean ext2 image, then flip ONE byte
of a directory entry's rec_len (16 -> 17, so rec_len % 4 != 0) in dir 'd'.
Usage: craft.py [workdir]
"""
import os, struct, subprocess, sys

def main(w='/tmp/opencode/e2work'):
    os.makedirs(w, exist_ok=True)
    img = os.path.join(w, 'mangle.img')
    for f in (img,):
        if os.path.exists(f):
            os.unlink(f)
    subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '1024',
                    '-I', '128', '-O', '^metadata_csum', img, '8192'],
                   check=True)
    for i in range(1, 6):
        with open(os.path.join(w, f'g{i}'), 'w') as f:
            f.write('x')
    cmds = os.path.join(w, 'mcmds.txt')
    with open(cmds, 'w') as f:
        f.write('mkdir d\ncd /d\n')
        for i in range(1, 6):
            f.write(f'write g{i} file{i}\n')
    subprocess.run(['debugfs', '-w', '-f', cmds, img],
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.run(['e2fsck', '-fy', img], stdout=subprocess.DEVNULL,
                   stderr=subprocess.DEVNULL)
    data = bytearray(open(img, 'rb').read())
    BS = 1024
    sb = bytes(data[1024:2048])
    ipg = struct.unpack('<I', sb[40:44])[0]
    grp, idx = (12 - 1) // ipg, (12 - 1) % ipg      # dir d is inode 12
    base = 2048 + grp * 32                          # group desc table (1k blocks)
    itbl = struct.unpack('<I', data[base + 8:base + 12])[0]
    ino = bytes(data[itbl * BS + idx * 128: itbl * BS + idx * 128 + 128])
    dblk = struct.unpack('<I', ino[40:44])[0] * BS  # i_block[0]
    o = 0
    while o < BS:
        rec, nlen = struct.unpack('<HB', data[dblk + o + 4:dblk + o + 7])
        name = bytes(data[dblk + o + 8:dblk + o + 8 + nlen]).decode()
        if name == 'file3':
            data[dblk + o + 4] = (rec + 1) & 0xff
            print(f'patched {name}: rec_len {rec} -> {rec + 1} (odd)')
            break
        if rec == 0:
            break
        o += rec
    open(img, 'wb').write(bytes(data))
    print('OK:', img)

if __name__ == '__main__':
    main(sys.argv[1] if len(sys.argv) > 1 else '/tmp/opencode/e2work')
