#!/usr/bin/env python3
"""DF-5847 diagnostic patcher -- edits /usr/src/sys/netinet6/nd6.c in place.
DIAGNOSTIC ONLY: never for upstream, never part of fix.diff."""
import sys

path = sys.argv[1] if len(sys.argv) > 1 else "/usr/src/sys/netinet6/nd6.c"
src = open(path).read()

DIAG = r'''
/* DF-5847 DIAGNOSTIC ONLY -- never for upstream */
struct nd6dbg {
	int		op;		/* 1=add 2=del 3=resolve 4=nd6free */
	int		cpu;
	void		*ln;
	void		*rt;
	void		*rt_llinfo;
	long		t;
	void		*ln_prev;
	void		*ln_next;
};
static struct nd6dbg nd6dbg_ring[64];
static volatile int nd6dbg_idx;

static void
nd6dbg_log(int op, void *lnp, void *rtp)
{
	struct nd6dbg *d;
	struct llinfo_nd6 *ln = lnp;
	int i = atomic_fetchadd_int(&nd6dbg_idx, 1);

	d = &nd6dbg_ring[i & 63];
	d->op = op;
	d->cpu = mycpuid;
	d->ln = lnp;
	d->rt = rtp;
	d->rt_llinfo = rtp ? ((struct rtentry *)rtp)->rt_llinfo : NULL;
	d->t = ticks;
	d->ln_prev = ln ? ln->ln_prev : NULL;
	d->ln_next = ln ? ln->ln_next : NULL;
}

static void
nd6dbg_dump(void)
{
	int i, n;

	kprintf("DF5847: ring dump (idx=%d):\n", nd6dbg_idx);
	n = nd6dbg_idx;
	for (i = n - 16; i < n; i++) {
		struct nd6dbg *d = &nd6dbg_ring[i & 63];
		kprintf("DF5847: [%d] op=%d cpu=%d ln=%p rt=%p rt_llinfo=%p "
		    "ln_prev=%p ln_next=%p t=%ld\n",
		    i, d->op, d->cpu, d->ln, d->rt, d->rt_llinfo,
		    d->ln_prev, d->ln_next, d->t);
	}
}
/* END DF-5847 DIAGNOSTIC */
'''

subs = [
# 1. infra block after nd6_mtx
("struct mtx nd6_mtx = MTX_INITIALIZER(\"nd6\");\n",
 "struct mtx nd6_mtx = MTX_INITIALIZER(\"nd6\");\n" + DIAG),
# 2. timer: tombstone catch + context dump on invariant trip
("""		if (rt->rt_llinfo && (struct llinfo_nd6 *)rt->rt_llinfo != ln)
			panic("rt_llinfo(%p) is not equal to ln(%p)",
			      rt->rt_llinfo, ln);
		if (!dst)
			panic("dst=0 in nd6_timer(ln=%p)", ln);""",
"""		/* DF-5847 DIAGNOSTIC: ln_prev==NULL is the RTM_DELETE
		 * tombstone -- walking an unlinked (freed) ln. */
		if (ln->ln_prev == NULL) {
			kprintf("DF5847: timer walked unlinked ln=%p rt=%p "
			    "cpu=%d\\n", ln, rt, mycpuid);
			nd6dbg_dump();
			panic("DF5847 walk-over-unlinked ln=%p", ln);
		}
		if (rt->rt_llinfo && (struct llinfo_nd6 *)rt->rt_llinfo != ln) {
			kprintf("DF5847: invariant trip ln=%p rt_llinfo=%p "
			    "cpu=%d\\n", ln, rt->rt_llinfo, mycpuid);
			nd6dbg_dump();
			panic("rt_llinfo(%p) is not equal to ln(%p)",
			      rt->rt_llinfo, ln);
		}
		if (!dst)
			panic("dst=0 in nd6_timer(ln=%p)", ln);"""),
# 3. nd6_free entry log
("""	struct in6_addr in6 = ((struct sockaddr_in6 *)rt_key(rt))->sin6_addr;
	struct nd_defrouter *dr;""",
 """	struct in6_addr in6 = ((struct sockaddr_in6 *)rt_key(rt))->sin6_addr;
	struct nd_defrouter *dr;

	nd6dbg_log(4, (void *)ln, (void *)rt);"""),
# 4. RTM_ADD/RESOLVE alloc log
("""		nd6_inuse++;
		nd6_allocated++;
		bzero(ln, sizeof(*ln));
		ln->ln_rt = rt;""",
 """		nd6_inuse++;
		nd6_allocated++;
		bzero(ln, sizeof(*ln));
		nd6dbg_log(req == RTM_ADD ? 1 : 3, (void *)ln, (void *)rt);
		ln->ln_rt = rt;"""),
# 5. RTM_DELETE unlink log
("""		nd6_inuse--;
		ln->ln_next->ln_prev = ln->ln_prev;""",
 """		nd6dbg_log(2, (void *)ln, (void *)rt);
		nd6_inuse--;
		ln->ln_next->ln_prev = ln->ln_prev;"""),
]

for old, new in subs:
    if src.count(old) != 1:
        print("ANCHOR FAIL (%d hits): %.60r" % (src.count(old), old))
        sys.exit(1)
    src = src.replace(old, new)

open(path, "w").write(src)
print("diag5847: patched OK")
