/*
 * DF-3269: userland reproduction of the deflateInit2_ allocation-failure
 * path against the *kernel source file itself* (sys/net/zlib.c compiled
 * without _KERNEL, which its own #ifdef ladder explicitly supports).
 *
 * deflateInit2_ allocates the deflate_state with the consumer zalloc and
 * only sets s->status in deflateReset (zlib.c:785-800 vs :1120 region).
 * If any of the follow-on allocations fails, the caller is expected to
 * run deflateEnd, which READS s->status (uninitialized!) and, unless the
 * garbage equals INIT/BUSY/FINISH_STATE, returns Z_STREAM_ERROR *before
 * any TRY_FREE* -> every allocation made so far leaks (zlib.c:1108-1127).
 *
 * The harness zalloc: (a) fills each allocation with 0xAA so the state is
 * provably uninitialized; (b) fails the Nth allocation on demand; (c)
 * counts live allocations, so the leak is measured, not argued.
 *
 * build: cc -O -o zharness zharness.c zlib_userland.c -I.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zlib.h"

static long allocs_live, allocs_total, alloc_bytes_live;
static int  fail_at = -1;             /* 1-based allocation ordinal */

static void *
count_zalloc(voidpf opaque, uInt items, uInt size)
{
	void *p;
	(void)opaque;
	allocs_total++;
	if (allocs_total == fail_at)
		return (Z_NULL);
	p = malloc((size_t)items * size);
	if (p) {
		memset(p, 0xAA, (size_t)items * size);   /* uninit state, visible */
		allocs_live++;
		alloc_bytes_live += (size_t)items * size;
	}
	return (p);
}

static void
count_zfree(voidpf opaque, voidpf ptr)
{
	(void)opaque;
	if (ptr) {
		/* recover size from our ledger: we simply track count */
		allocs_live--;
		free(ptr);
	}
}

int main(void)
{
	z_stream z;
	int r;

	/* --- case 1: window allocation fails (2nd alloc) --- */
	memset(&z, 0, sizeof(z));
	z.zalloc = count_zalloc; z.zfree = count_zfree; z.opaque = Z_NULL;
	allocs_live = allocs_total = 0; fail_at = 2;

	printf("case 1: fail the window allocation (alloc #2)\n");
	r = deflateInit2_(&z, 6, Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY,
			  ZLIB_VERSION, (int)sizeof(z));
	printf("  deflateInit2_ -> %d\n", r);
	printf("  allocs made=%ld\n", allocs_total);

	/* kernel/ng_deflate error path: call deflateEnd on the failed init */
	r = deflateEnd(&z);
	printf("  deflateEnd  -> %d  (%s)\n", r,
	       r == Z_STREAM_ERROR ? "Z_STREAM_ERROR: status garbage" :
	       r == Z_OK ? "Z_OK: cleanup ran" : "other");
	printf("  live allocations after deflateEnd: %ld  %s\n", allocs_live,
	       allocs_live ? "<<<< LEAKED" : "(none leaked)");

	/* --- case 2: no induced failure, normal round trip sanity --- */
	memset(&z, 0, sizeof(z));
	z.zalloc = count_zalloc; z.zfree = count_zfree; z.opaque = Z_NULL;
	allocs_live = allocs_total = 0; fail_at = -1;
	r = deflateInit2_(&z, 6, Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY,
			  ZLIB_VERSION, (int)sizeof(z));
	printf("case 2: no failure: init -> %d, deflateEnd -> %d, live=%ld\n",
	       r, deflateEnd(&z), allocs_live);
	return 0;
}
