/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2025 Oxide Computer Company
 */

#include <mdb/mdb_modapi.h>
#include <sys/rctl.h>
#include <sys/proc.h>
#include <sys/task.h>
#include <sys/project.h>
#include <sys/zone.h>

static int
print_val(uintptr_t addr, rctl_val_t *val, uintptr_t *enforced)
{
	char *priv;
	static const mdb_bitmask_t val_localflag_bits[] = {
		{ "SIGNAL", RCTL_LOCAL_SIGNAL, RCTL_LOCAL_SIGNAL },
		{ "DENY", RCTL_LOCAL_DENY, RCTL_LOCAL_DENY },
		{ "MAX", RCTL_LOCAL_MAXIMAL, RCTL_LOCAL_MAXIMAL },
		{ NULL, 0, 0 }
	};

	switch (val->rcv_privilege) {
	case (RCPRIV_BASIC):
		priv = "basic";
		break;
	case (RCPRIV_PRIVILEGED):
		priv = "privileged";
		break;
	case (RCPRIV_SYSTEM):
		priv = "system";
		break;
	default:
		priv = "???";
		break;
	};

	mdb_printf("\t%s ", addr == *enforced ? "(cur)": "     ");

	mdb_printf("%-#18llx %11s\tflags=<%b>\n",
	    val->rcv_value, priv, val->rcv_flagaction, val_localflag_bits);

	return (WALK_NEXT);
}

int
rctl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	rctl_t rctl;
	rctl_dict_entry_t dict;
	char name[256];
	rctl_hndl_t hndl;

	if (!(flags & DCMD_ADDRSPEC))
		return (DCMD_USAGE);

	if (mdb_vread(&rctl, sizeof (rctl_t), addr) == -1) {
		mdb_warn("failed to read rctl_t structure at %p", addr);
		return (DCMD_ERR);
	}

	if (argc != 0) {
		const mdb_arg_t *argp = &argv[0];

		hndl = (rctl_hndl_t)mdb_argtoull(argp);

		if (rctl.rc_id != hndl)
			return (DCMD_OK);
	}

	if (mdb_vread(&dict, sizeof (rctl_dict_entry_t),
	    (uintptr_t)rctl.rc_dict_entry) == -1) {
		mdb_warn("failed to read dict entry for rctl_t %p at %p",
		    addr, rctl.rc_dict_entry);
		return (DCMD_ERR);
	}

	if (mdb_readstr(name, 256, (uintptr_t)(dict.rcd_name)) == -1) {
		mdb_warn("failed to read name for rctl_t %p", addr);
		return (DCMD_ERR);
	}

	mdb_printf("%0?p\t%3d : %s\n", addr, rctl.rc_id, name);

	if (mdb_pwalk("rctl_val", (mdb_walk_cb_t)print_val, &(rctl.rc_cursor),
	    addr) == -1) {
		mdb_warn("failed to walk all values for rctl_t %p", addr);
		return (DCMD_ERR);
	}

	return (DCMD_OK);
}

int
rctl_dict(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	rctl_dict_entry_t dict;
	char name[256], *type = NULL;

	if (!(flags & DCMD_ADDRSPEC)) {
		if (mdb_walk_dcmd("rctl_dict_list", "rctl_dict", argc,
		    argv) == -1) {
			mdb_warn("failed to walk 'rctl_dict_list'");
			return (DCMD_ERR);
		}
		return (DCMD_OK);
	}

	if (DCMD_HDRSPEC(flags))
		mdb_printf("%<u>%2s %-27s %?s %7s %s%</u>\n",
		    "ID", "NAME", "ADDR", "TYPE", "GLOBAL_FLAGS");

	if (mdb_vread(&dict, sizeof (dict), addr) == -1) {
		mdb_warn("failed to read rctl_dict at %p", addr);
		return (DCMD_ERR);
	}
	if (mdb_readstr(name, 256, (uintptr_t)(dict.rcd_name)) == -1) {
		mdb_warn("failed to read rctl_dict name for %p", addr);
		return (DCMD_ERR);
	}

	switch (dict.rcd_entity) {
	case RCENTITY_PROCESS:
		type = "process";
		break;
	case RCENTITY_TASK:
		type = "task";
		break;
	case RCENTITY_PROJECT:
		type = "project";
		break;
	case RCENTITY_ZONE:
		type = "zone";
		break;
	default:
		type = "unknown";
		break;
	}

	mdb_printf("%2d %-27s %0?p %7s 0x%08x", dict.rcd_id, name, addr,
	    type, dict.rcd_flagaction);

	return (DCMD_OK);
}

typedef struct dict_data {
	rctl_hndl_t hndl;
	uintptr_t dict_addr;
	rctl_entity_t type;
} dict_data_t;

static int
hndl2dict(uintptr_t addr, rctl_dict_entry_t *entry, dict_data_t *data)
{
	if (data->hndl == entry->rcd_id) {
		data->dict_addr = addr;
		data->type = entry->rcd_entity;
		return (WALK_DONE);
	}

	return (WALK_NEXT);
}

/*
 * Print out all project, task, and process rctls for a given process.
 * If a handle is specified, print only the rctl matching that handle
 * for the process.
 */
int
rctl_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	proc_t proc;
	uintptr_t set;
	task_t task;
	kproject_t proj;
	zone_t zone;
	dict_data_t rdict;
	int i;

	rdict.dict_addr = 0;

	if (!(flags & DCMD_ADDRSPEC))
		return (DCMD_USAGE);

	if (argc == 0)
		rdict.hndl = 0;
	else if (argc == 1) {
		/*
		 * User specified a handle. Go find the rctl_dict_entity_t
		 * structure so we know what type of rctl to look for.
		 */
		const mdb_arg_t *argp = &argv[0];

		rdict.hndl = (rctl_hndl_t)mdb_argtoull(argp);

		if (mdb_walk("rctl_dict_list", (mdb_walk_cb_t)hndl2dict,
		    &rdict) == -1) {
			mdb_warn("failed to walk rctl_dict_list");
			return (DCMD_ERR);
		}
		/* Couldn't find a rctl_dict_entry_t for this handle */
		if (rdict.dict_addr == 0)
			return (DCMD_ERR);
	} else
		return (DCMD_USAGE);


	if (mdb_vread(&proc, sizeof (proc_t), addr) == -1) {
		mdb_warn("failed to read proc at %p", addr);
		return (DCMD_ERR);
	}
	if (mdb_vread(&zone, sizeof (zone_t), (uintptr_t)proc.p_zone) == -1) {
		mdb_warn("failed to read zone at %p", proc.p_zone);
		return (DCMD_ERR);
	}
	if (mdb_vread(&task, sizeof (task_t), (uintptr_t)proc.p_task) == -1) {
		mdb_warn("failed to read task at %p", proc.p_task);
		return (DCMD_ERR);
	}
	if (mdb_vread(&proj, sizeof (kproject_t),
	    (uintptr_t)task.tk_proj) == -1) {
		mdb_warn("failed to read proj at %p", task.tk_proj);
		return (DCMD_ERR);
	}

	for (i = 0; i <= RC_MAX_ENTITY; i++) {
		/*
		 * If user didn't specify a handle, print rctls for all
		 * types. Otherwise, we can walk the rctl_set for only the
		 * entity specified by the handle.
		 */
		if (rdict.hndl != 0 && rdict.type != i)
			continue;

		switch (i) {
		case (RCENTITY_PROCESS):
			set = (uintptr_t)proc.p_rctls;
			break;
		case (RCENTITY_TASK):
			set = (uintptr_t)task.tk_rctls;
			break;
		case (RCENTITY_PROJECT):
			set = (uintptr_t)proj.kpj_rctls;
			break;
		case (RCENTITY_ZONE):
			set = (uintptr_t)zone.zone_rctls;
			break;
		default:
			mdb_warn("Unknown rctl type %d", i);
			return (DCMD_ERR);
		}

		if (mdb_pwalk_dcmd("rctl_set", "rctl", argc, argv, set) == -1) {
			mdb_warn("failed to walk rctls in set %p", set);
			return (DCMD_ERR);
		}
	}

	return (DCMD_OK);
}

typedef struct dict_walk_data {
	int num_dicts;
	int num_cur;
	rctl_dict_entry_t **curdict;
} dict_walk_data_t;

int
rctl_dict_walk_init(mdb_walk_state_t *wsp)
{
	uintptr_t ptr;
	int nlists;
	GElf_Sym sym;
	rctl_dict_entry_t **dicts;
	dict_walk_data_t *dwd;

	if (mdb_lookup_by_name("rctl_lists", &sym) == -1) {
		mdb_warn("failed to find 'rctl_lists'\n");
		return (WALK_ERR);
	}

	nlists = sym.st_size / sizeof (rctl_dict_entry_t *);
	ptr = (uintptr_t)sym.st_value;

	dicts = mdb_alloc(nlists * sizeof (rctl_dict_entry_t *), UM_SLEEP);
	mdb_vread(dicts, sym.st_size, ptr);

	dwd = mdb_alloc(sizeof (dict_walk_data_t), UM_SLEEP);
	dwd->num_dicts = nlists;
	dwd->num_cur = 0;
	dwd->curdict = dicts;

	wsp->walk_addr = 0;
	wsp->walk_data = dwd;

	return (WALK_NEXT);
}

int
rctl_dict_walk_step(mdb_walk_state_t *wsp)
{
	dict_walk_data_t *dwd = wsp->walk_data;
	uintptr_t dp;
	rctl_dict_entry_t entry;
	int status;

	dp = (uintptr_t)((dwd->curdict)[dwd->num_cur]);

	while (dp != 0) {
		if (mdb_vread(&entry, sizeof (rctl_dict_entry_t), dp) == -1) {
			mdb_warn("failed to read rctl_dict_entry_t structure "
			    "at %p", dp);
			return (WALK_ERR);
		}

		status = wsp->walk_callback(dp, &entry, wsp->walk_cbdata);
		if (status != WALK_NEXT)
			return (status);

		dp = (uintptr_t)entry.rcd_next;
	}

	dwd->num_cur++;

	if (dwd->num_cur == dwd->num_dicts)
		return (WALK_DONE);

	return (WALK_NEXT);
}

void
rctl_dict_walk_fini(mdb_walk_state_t *wsp)
{
	dict_walk_data_t *wd = wsp->walk_data;
	mdb_free(wd->curdict, wd->num_dicts * sizeof (rctl_dict_entry_t *));
	mdb_free(wd, sizeof (dict_walk_data_t));
}

typedef struct set_walk_data {
	uint_t hashsize;
	int hashcur;
	void **hashloc;
} set_walk_data_t;

int
rctl_set_walk_init(mdb_walk_state_t *wsp)
{
	rctl_set_t rset;
	uint_t hashsz;
	set_walk_data_t *swd;
	rctl_t **rctls;

	if (mdb_vread(&rset, sizeof (rctl_set_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to read rset at %p", wsp->walk_addr);
		return (WALK_ERR);
	}

	if (mdb_readvar(&hashsz, "rctl_set_size") == -1 || hashsz == 0) {
		mdb_warn("rctl_set_size not found or invalid");
		return (WALK_ERR);
	}

	rctls = mdb_alloc(hashsz * sizeof (rctl_t *), UM_SLEEP);
	if (mdb_vread(rctls, hashsz * sizeof (rctl_t *),
	    (uintptr_t)rset.rcs_ctls) == -1) {
		mdb_warn("cannot read rctl hash at %p", rset.rcs_ctls);
		mdb_free(rctls, hashsz * sizeof (rctl_t *));
		return (WALK_ERR);
	}

	swd = mdb_alloc(sizeof (set_walk_data_t), UM_SLEEP);
	swd->hashsize = hashsz;
	swd->hashcur = 0;
	swd->hashloc = (void **)rctls;

	wsp->walk_addr = 0;
	wsp->walk_data = swd;

	return (WALK_NEXT);
}


int
rctl_set_walk_step(mdb_walk_state_t *wsp)
{
	set_walk_data_t	*swd = wsp->walk_data;
	rctl_t rctl;
	void **rhash = swd->hashloc;
	int status;

	if (swd->hashcur >= swd->hashsize)
		return (WALK_DONE);

	if (wsp->walk_addr == 0) {
		while (swd->hashcur < swd->hashsize) {
			if (rhash[swd->hashcur] != NULL) {
				break;
			}
			swd->hashcur++;
		}

		if (rhash[swd->hashcur] == NULL ||
		    swd->hashcur >= swd->hashsize)
			return (WALK_DONE);

		wsp->walk_addr = (uintptr_t)rhash[swd->hashcur];
		swd->hashcur++;
	}

	if (mdb_vread(&rctl, sizeof (rctl_t), wsp->walk_addr) == -1) {
		wsp->walk_addr = 0;
		mdb_warn("unable to read from %#p", wsp->walk_addr);
		return (WALK_ERR);
	}

	status = wsp->walk_callback(wsp->walk_addr, &rctl, wsp->walk_cbdata);

	wsp->walk_addr = (uintptr_t)rctl.rc_next;

	return (status);
}

void
rctl_set_walk_fini(mdb_walk_state_t *wsp)
{
	set_walk_data_t *sd = wsp->walk_data;

	mdb_free(sd->hashloc, sd->hashsize * sizeof (rctl_t *));
	mdb_free(sd, sizeof (set_walk_data_t));
}

int
rctl_val_walk_init(mdb_walk_state_t *wsp)
{
	rctl_t rctl;

	if (mdb_vread(&rctl, sizeof (rctl_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to read rctl at %p", wsp->walk_addr);
		return (WALK_ERR);
	}
	wsp->walk_addr = (uintptr_t)rctl.rc_values;
	wsp->walk_data = rctl.rc_values;
	return (WALK_NEXT);
}

int
rctl_val_walk_step(mdb_walk_state_t *wsp)
{
	rctl_val_t val;
	int status;

	if (mdb_vread(&val, sizeof (rctl_val_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to read rctl_val at %p", wsp->walk_addr);
		return (WALK_DONE);
	}

	status = wsp->walk_callback(wsp->walk_addr, &val, wsp->walk_cbdata);

	if ((wsp->walk_addr = (uintptr_t)val.rcv_next) == 0)
		return (WALK_DONE);

	return (status);
}

typedef struct rctl_val_seen {
	uintptr_t		s_ptr;
	rctl_qty_t		s_val;
} rctl_val_seen_t;

typedef struct rctl_validate_data {
	uintptr_t		v_rctl_addr;
	rctl_val_t		*v_cursor;
	uint_t			v_flags;
	int			v_bad_rctl;
	int			v_cursor_valid;
	int			v_circularity_detected;
	uint_t			v_seen_size;
	uint_t			v_seen_cnt;
	rctl_val_seen_t		*v_seen;
} rctl_validate_data_t;

#define	RCV_VERBOSE 0x1

/*
 * rctl_val_validate()
 * Do validation on an individual rctl_val_t. This function is called
 * as part of the rctl_val walker, and helps perform the checks described
 * in the ::rctl_validate dcmd.
 */
static int
rctl_val_validate(uintptr_t addr, rctl_val_t *val, rctl_validate_data_t *data)
{
	int i;

	data->v_seen[data->v_seen_cnt].s_ptr = addr;

	if (addr == (uintptr_t)data->v_cursor)
		data->v_cursor_valid++;

	data->v_seen[data->v_seen_cnt].s_val = val->rcv_value;

	if (val->rcv_prev == (void *)0xbaddcafe ||
	    val->rcv_next == (void *)0xbaddcafe ||
	    val->rcv_prev == (void *)0xdeadbeef ||
	    val->rcv_next == (void *)0xdeadbeef) {
		if (data->v_bad_rctl++ == 0)
			mdb_printf("%p ", data->v_rctl_addr);
		if (data->v_flags & RCV_VERBOSE)
			mdb_printf("/ uninitialized or previously "
			    "freed link at %p ", addr);
	}

	if (data->v_seen_cnt == 0) {
		if (val->rcv_prev != NULL) {
			if (data->v_bad_rctl++ == 0)
				mdb_printf("%p ", data->v_rctl_addr);
			if (data->v_flags & RCV_VERBOSE)
				mdb_printf("/ bad prev pointer at "
				    "head ");
		}
	} else {
		if ((uintptr_t)val->rcv_prev !=
		    data->v_seen[data->v_seen_cnt - 1].s_ptr) {
			if (data->v_bad_rctl++ == 0)
				mdb_printf("%p ", data->v_rctl_addr);
			if (data->v_flags & RCV_VERBOSE)
				mdb_printf("/ bad prev pointer at %p ",
				    addr);
		}

		if (data->v_seen[data->v_seen_cnt].s_val <
		    data->v_seen[data->v_seen_cnt - 1].s_val) {
			if (data->v_bad_rctl++ == 0)
				mdb_printf("%p ", data->v_rctl_addr);
			if (data->v_flags & RCV_VERBOSE)
				mdb_printf("/ ordering error at %p ",
				    addr);
		}
	}

	for (i = data->v_seen_cnt; i >= 0; i--) {
		if (data->v_seen[i].s_ptr == (uintptr_t)val->rcv_next) {
			if (data->v_bad_rctl++ == 0)
				mdb_printf("%p ", data->v_rctl_addr);
			if (data->v_flags & RCV_VERBOSE)
				mdb_printf("/ circular next pointer "
				    "at %p ", addr);
			data->v_circularity_detected++;
			break;
		}
	}

	if (data->v_circularity_detected)
		return (WALK_DONE);

	data->v_seen_cnt++;
	if (data->v_seen_cnt >= data->v_seen_size) {
		uint_t new_seen_size = data->v_seen_size * 2;
		rctl_val_seen_t *tseen = mdb_zalloc(new_seen_size *
		    sizeof (rctl_val_seen_t), UM_SLEEP | UM_GC);

		bcopy(data->v_seen, tseen, data->v_seen_size *
		    sizeof (rctl_val_seen_t));

		data->v_seen = tseen;
		data->v_seen_size = new_seen_size;
	}

	return (WALK_NEXT);
}

/*
 * Validate a rctl pointer by checking:
 *   - rctl_val_t's for that rctl form an ordered, non-circular list
 *   - the cursor points to a rctl_val_t within that list
 *   - there are no more than UINT64_MAX (or # specified by -n)
 *     rctl_val_t's in the list
 */
int
rctl_validate(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	rctl_validate_data_t data;

	rctl_t r;

	uint64_t long_threshold = UINT64_MAX;

	/* Initialize validate data structure */
	data.v_rctl_addr = addr;
	data.v_flags = 0;
	data.v_bad_rctl = 0;
	data.v_seen_cnt = 0;
	data.v_cursor_valid = 0;
	data.v_circularity_detected = 0;
	data.v_seen_size = 1;
	data.v_seen = mdb_zalloc(data.v_seen_size * sizeof (rctl_val_seen_t),
	    UM_SLEEP | UM_GC);

	if (!(flags & DCMD_ADDRSPEC))
		return (DCMD_USAGE);

	if (mdb_getopts(argc, argv,
	    'v', MDB_OPT_SETBITS, RCV_VERBOSE, &data.v_flags,
	    'n', MDB_OPT_UINT64, &long_threshold,
	    NULL) != argc)
		return (DCMD_USAGE);

	if (mdb_vread(&r, sizeof (rctl_t), addr) != sizeof (rctl_t)) {
		mdb_warn("failed to read rctl structure at %p", addr);
		return (DCMD_ERR);
	}

	data.v_cursor = r.rc_cursor;

	if (data.v_cursor == NULL) {
		if (data.v_bad_rctl++ == 0)
			mdb_printf("%p ", addr);
		if (data.v_flags & RCV_VERBOSE)
			mdb_printf("/ NULL cursor seen ");
	} else if (data.v_cursor == (rctl_val_t *)0xbaddcafe) {
		if (data.v_bad_rctl++ == 0)
			mdb_printf("%p ", addr);
		if (data.v_flags & RCV_VERBOSE)
			mdb_printf("/ uninitialized cursor seen ");
	}

	/* Walk through each val in this rctl for individual validation. */
	if (mdb_pwalk("rctl_val", (mdb_walk_cb_t)rctl_val_validate, &data,
	    addr) == -1) {
		mdb_warn("failed to walk all values for rctl_t %p", addr);
		return (DCMD_ERR);
	}

	if (data.v_seen_cnt >= long_threshold) {
		if (data.v_bad_rctl++ == 0)
			mdb_printf("%p ", addr);
		if (data.v_flags & RCV_VERBOSE)
			mdb_printf("/ sequence length = %d ",
			    data.v_seen_cnt);
	}

	if (!data.v_cursor_valid) {
		if (data.v_bad_rctl++ == 0)
			mdb_printf("%p ", addr);
		if (data.v_flags & RCV_VERBOSE)
			mdb_printf("/ cursor outside sequence");
	}

	if (data.v_bad_rctl)
		mdb_printf("\n");

	if (data.v_circularity_detected)
		mdb_warn("circular list implies possible memory leak; "
		    "recommend invoking ::findleaks");

	return (DCMD_OK);
}
