#!/usr/bin/env python3
# cg256_holder.py — allocate N kmalloc-cg-256 objects via msg_msg (200B body
# + ~48B header rounds to 256, GFP_KERNEL_ACCOUNT). Holds them alive until
# SIGTERM, then drops every queue (msgctl IPC_RMID) so all pages free.
#
# Uses N separate sysv message queues (1 msg per queue) to dodge the 8KB
# per-queue msgmnb limit.
#
# Usage:
#   ./cg256_holder.py <count>
#   send SIGTERM/SIGUSR1 to free.

import ctypes, os, sys, signal

libc = ctypes.CDLL("libc.so.6", use_errno=True)
IPC_PRIVATE = 0
IPC_CREAT = 0o1000
IPC_RMID = 0

class Msg(ctypes.Structure):
    _fields_ = [("mtype", ctypes.c_long), ("mtext", ctypes.c_char * 200)]

def main():
    count = int(sys.argv[1]) if len(sys.argv) > 1 else 384
    qids = []
    m = Msg(); m.mtype = 1; m.mtext = b"A" * 200
    for i in range(count):
        q = libc.msgget(IPC_PRIVATE, IPC_CREAT | 0o600)
        if q < 0:
            err = ctypes.get_errno()
            print(f"msgget #{i} err={err}", flush=True); break
        qids.append(q)
        r = libc.msgsnd(q, ctypes.byref(m), 200, 0)
        if r != 0:
            err = ctypes.get_errno()
            print(f"msgsnd #{i} err={err}", flush=True); break
    print(f"qids={len(qids)} pid={os.getpid()}", flush=True)

    free_now = False
    def free_handler(signum, frame):
        nonlocal free_now
        free_now = True
    signal.signal(signal.SIGTERM, free_handler)
    signal.signal(signal.SIGUSR1, free_handler)

    while not free_now:
        signal.pause()

    print("freeing…", flush=True)
    for q in qids:
        libc.msgctl(q, IPC_RMID, None)
    print("done", flush=True)

if __name__ == "__main__":
    main()
