/*
 * Oracle Linux DTrace.
 * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

#include <sys/types.h>
#include <sys/bitmap.h>

#include <assert.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <alloca.h>
#include <unistd.h>
#include <errno.h>
#include <port.h>

#include <dt_probe.h>
#include <dt_module.h>
#include <dt_string.h>
#include <dt_htab.h>
#include <dt_list.h>
#include <dt_bpf.h>

typedef struct dt_probe_stmt {
	dt_list_t		list;
	dtrace_stmtdesc_t	*stmt;
} dt_probe_stmt_t;

typedef struct dt_probe_dependent {
	dt_list_t	list;
	dt_probe_t	*probe;
} dt_probe_dependent_t;

#define DEFINE_HE_FUNCS(id) \
	static uint32_t id##_hval(const dt_probe_t *probe) \
	{ \
		return str2hval(probe->desc->id, 0); \
	} \
	\
	static int id##_cmp(const dt_probe_t *p, \
			    const dt_probe_t *q) \
	{ \
		return strcmp(p->desc->id, q->desc->id); \
	} \
	\
	DEFINE_HE_LINK_FUNCS(id)

#define DEFINE_HE_LINK_FUNCS(id) \
	DEFINE_HE_STD_LINK_FUNCS(id, dt_probe_t, he_##id)

DEFINE_HE_FUNCS(prv)
DEFINE_HE_FUNCS(mod)
DEFINE_HE_FUNCS(fun)
DEFINE_HE_FUNCS(prb)

/*
 * Calculate the hash value of a probe as the cummulative hash value of the
 * FQN.
 */
static uint32_t fqn_hval(const dt_probe_t *probe)
{
	uint32_t	hval = 0;

	hval = str2hval(probe->desc->prv, hval);
	hval = str2hval(":", hval);
	hval = str2hval(probe->desc->mod, hval);
	hval = str2hval(":", hval);
	hval = str2hval(probe->desc->fun, hval);
	hval = str2hval(":", hval);
	hval = str2hval(probe->desc->prb, hval);

	return hval;
}

/* Compare two probes based on the FQN. */
static int fqn_cmp(const dt_probe_t *p, const dt_probe_t *q)
{
	int	rc;

	rc = strcmp(p->desc->prv, q->desc->prv);
	if (rc)
		return rc;
	rc = strcmp(p->desc->mod, q->desc->mod);
	if (rc)
		return rc;
	rc = strcmp(p->desc->fun, q->desc->fun);
	if (rc)
		return rc;
	rc = strcmp(p->desc->prb, q->desc->prb);
	if (rc)
		return rc;

	return 0;
}

DEFINE_HE_LINK_FUNCS(fqn)

DEFINE_HTAB_STD_OPS(prv)
DEFINE_HTAB_STD_OPS(mod)
DEFINE_HTAB_STD_OPS(fun)
DEFINE_HTAB_STD_OPS(prb)
DEFINE_HTAB_STD_OPS(fqn)

static uint8_t
dt_probe_argmap(dt_node_t *xnp, dt_node_t *nnp)
{
	uint8_t i;

	for (i = 0; nnp != NULL; i++) {
		if (nnp->dn_string != NULL &&
		    strcmp(nnp->dn_string, xnp->dn_string) == 0)
			break;
		else
			nnp = nnp->dn_list;
	}

	return i;
}

static dt_node_t *
alloc_arg_nodes(dtrace_hdl_t *dtp, dt_provider_t *pvp, int argc)
{
	dt_node_t	*dnp = NULL;
	int		i;

	for (i = 0; i < argc; i++) {
		if ((dnp = dt_node_xalloc(dtp, DT_NODE_TYPE)) == NULL)
			return NULL;

		dnp->dn_link = pvp->pv_nodes;
		pvp->pv_nodes = dnp;
	}

	return dnp;
}

static void
dt_probe_alloc_args(dt_probe_t *prp, int nargc, int xargc)
{
	dt_provider_t	*pvp = prp->prov;
	dtrace_hdl_t	*dtp = pvp->pv_hdl;
	dt_node_t	*nargs = NULL, *xargs = NULL;
	int		i;

	prp->nargs = alloc_arg_nodes(dtp, prp->prov, nargc);
	prp->nargv = dt_calloc(dtp, nargc, sizeof(dt_node_t *));
	prp->nargc = nargc;
	prp->xargs = alloc_arg_nodes(dtp, prp->prov, xargc);
	prp->xargv = dt_calloc(dtp, xargc, sizeof(dt_node_t *));
	prp->xargc = xargc;
	prp->mapping = dt_calloc(dtp, xargc, sizeof(uint8_t));
	prp->argv = dt_calloc(dtp, xargc, sizeof(dtrace_typeinfo_t));
	prp->argc = xargc;

	for (i = 0, xargs = prp->xargs;
	     i < xargc;
	     i++, xargs = xargs->dn_link) {
		prp->mapping[i] = i;
		prp->xargv[i] = xargs;
		prp->argv[i].dtt_object = NULL;
		prp->argv[i].dtt_ctfp = NULL;
		prp->argv[i].dtt_type = CTF_ERR;
	}

	for (i = 0, nargs = prp->nargs;
	     i < nargc;
	     i++, nargs = nargs->dn_link) {
		prp->nargv[i] = nargs;
	}
}

/*
 * Lookup a probe declaration based on a known provider and full or partially
 * specified module, function, and name.
 */
dt_probe_t *
dt_probe_lookup2(dt_provider_t *pvp, const char *s)
{
	dtrace_hdl_t *dtp = pvp->pv_hdl;
	dtrace_probedesc_t pd;
	dt_ident_t *idp;
	char *key;

	if (dtrace_str2desc(dtp, DTRACE_PROBESPEC_NAME, s, &pd) != 0)
		return NULL; /* dt_errno is set for us */

	if (asprintf(&key, "%s:%s:%s", pd.mod, pd.fun, pd.prb) == -1) {
		dt_set_errno(dtp, errno);
		goto out;
	}

	/*
	 * If the probe is already declared, then return the dt_probe_t from
	 * the existing identifier.
	 */
	if ((idp = dt_idhash_lookup(pvp->pv_probes, key)) != NULL) {
		dt_desc_destroy(dtp, &pd, 0);
		return idp->di_data;
	}

	dt_set_errno(dtp, EDT_NOPROBE);
 out:
	dt_desc_destroy(dtp, &pd, 0);
	return NULL;
}

dt_probe_t *
dt_probe_create(dtrace_hdl_t *dtp, dt_ident_t *idp, int protoc,
		dt_node_t *nargs, uint_t nargc, dt_node_t *xargs, uint_t xargc)
{
	dt_module_t *dmp;
	dt_probe_t *prp;
	const char *p;
	uint_t i;

	assert(idp->di_kind == DT_IDENT_PROBE);
	assert(idp->di_data == NULL);

	/*
	 * If only a single prototype is given, set xargc/s to nargc/s to
	 * simplify subsequent use.  Note that we can have one or both of nargs
	 * and xargs be specified but set to NULL, indicating a void prototype.
	 */
	if (protoc < 2) {
		assert(xargs == NULL);
		assert(xargc == 0);
		xargs = nargs;
		xargc = nargc;
	}

	prp = dt_zalloc(dtp, sizeof(dt_probe_t));
	if (prp == NULL)
		return NULL;

	prp->prov = NULL;
	prp->pr_ident = idp;

	p = strrchr(idp->di_name, ':');
	assert(p != NULL);
	prp->pr_name = p + 1;

	prp->nargs = nargs;
	prp->nargv = dt_calloc(dtp, nargc, sizeof(dt_node_t *));
	prp->nargc = nargc;
	prp->xargs = xargs;
	prp->xargv = dt_calloc(dtp, xargc, sizeof(dt_node_t *));
	prp->xargc = xargc;
	prp->mapping = dt_calloc(dtp, xargc, sizeof(uint8_t));
	prp->pr_inst = NULL;
	prp->argv = dt_calloc(dtp, xargc, sizeof(dtrace_typeinfo_t));
	prp->argc = xargc;

	if ((prp->nargc != 0 && prp->nargv == NULL) ||
	    (prp->xargc != 0 && prp->xargv == NULL) ||
	    (prp->xargc != 0 && prp->mapping == NULL) ||
	    (prp->argc != 0 && prp->argv == NULL)) {
		dt_probe_destroy(prp);
		return NULL;
	}

	for (i = 0; i < xargc; i++, xargs = xargs->dn_list) {
		if (xargs->dn_string != NULL)
			prp->mapping[i] = dt_probe_argmap(xargs, nargs);
		else
			prp->mapping[i] = i;

		prp->xargv[i] = xargs;

		if ((dmp = dt_module_lookup_by_ctf(dtp,
		    xargs->dn_ctfp)) != NULL)
			prp->argv[i].dtt_object = dmp->dm_name;
		else
			prp->argv[i].dtt_object = NULL;

		prp->argv[i].dtt_ctfp = xargs->dn_ctfp;
		prp->argv[i].dtt_type = xargs->dn_type;
	}

	for (i = 0; i < nargc; i++, nargs = nargs->dn_list)
		prp->nargv[i] = nargs;

	idp->di_data = prp;

	return prp;
}

void
dt_probe_declare(dt_provider_t *pvp, dt_probe_t *prp)
{
	assert(prp->pr_ident->di_kind == DT_IDENT_PROBE);
	assert(prp->pr_ident->di_data == prp);
	assert(prp->prov == NULL);

	if (prp->xargs != prp->nargs)
		pvp->pv_flags &= ~DT_PROVIDER_INTF;

	prp->prov = pvp;
	dt_idhash_xinsert(pvp->pv_probes, prp->pr_ident);
}

void
dt_probe_enable(dtrace_hdl_t *dtp, dt_probe_t *prp)
{
	assert(prp->prov->impl != NULL);
	if (prp->prov->impl->enable == NULL) {
		if (!dt_in_list(&dtp->dt_enablings, prp))
			dt_list_append(&dtp->dt_enablings, prp);
	} else
		prp->prov->impl->enable(dtp, prp);

	dt_strtab_insert(dtp->dt_ccstab, prp->desc->prv);
	dt_strtab_insert(dtp->dt_ccstab, prp->desc->mod);
	dt_strtab_insert(dtp->dt_ccstab, prp->desc->fun);
	dt_strtab_insert(dtp->dt_ccstab, prp->desc->prb);
}

void
dt_probe_destroy(dt_probe_t *prp)
{
	dt_probe_stmt_t		*psp, *psp_next;
	dt_probe_instance_t	*pip, *pip_next;
	dt_probe_dependent_t	*dep, *dep_next;
	dtrace_hdl_t		*dtp;

	if (prp->prov != NULL)
		dtp = prp->prov->pv_hdl;
	else
		dtp = yypcb->pcb_hdl;

	if (prp->difo)
		dt_difo_free(dtp, prp->difo);

	if (prp->desc) {
		dtp->dt_probes[prp->desc->id] = NULL;

		dt_htab_delete(dtp->dt_byprv, prp);
		dt_htab_delete(dtp->dt_bymod, prp);
		dt_htab_delete(dtp->dt_byfun, prp);
		dt_htab_delete(dtp->dt_byprb, prp);
		dt_htab_delete(dtp->dt_byfqn, prp);
	}

	if (prp->prov && prp->prov->impl && prp->prov->impl->probe_destroy)
		prp->prov->impl->probe_destroy(dtp, prp->prv_data);

	dt_node_list_free(&prp->nargs);
	dt_node_list_free(&prp->xargs);

	dt_free(dtp, prp->nargv);
	dt_free(dtp, prp->xargv);

	for (psp = dt_list_next(&prp->stmts); psp != NULL; psp = psp_next) {
		psp_next = dt_list_next(psp);
		dt_free(dtp, psp);
	}

	for (dep = dt_list_next(&prp->dependents); dep != NULL; dep = dep_next) {
		dep_next = dt_list_next(dep);
		dt_free(dtp, dep);
	}

	for (pip = prp->pr_inst; pip != NULL; pip = pip_next) {
		pip_next = pip->pi_next;
		dt_free(dtp, pip->pi_offs);
		dt_free(dtp, pip->pi_enoffs);
		dt_free(dtp, pip);
	}

	dt_free(dtp, prp->mapping);
	dt_free(dtp, prp->argv);
	dt_desc_destroy(dtp, (dtrace_probedesc_t *)prp->desc, 1);
	dt_free(dtp, prp);
}

int
dt_probe_define(dt_provider_t *pvp, dt_probe_t *prp, const char *fname,
		const char *rname, uint32_t offset, int isenabled)
{
	dtrace_hdl_t *dtp = pvp->pv_hdl;
	dt_probe_instance_t *pip;
	uint32_t **offs;
	uint_t *noffs, *maxoffs;

	assert(fname != NULL);

	for (pip = prp->pr_inst; pip != NULL; pip = pip->pi_next) {
		if (strcmp(pip->pi_fname, fname) == 0 &&
		    ((rname == NULL && pip->pi_rname[0] == '\0') ||
		    (rname != NULL && strcmp(pip->pi_rname, rname)) == 0))
			break;
	}

	if (pip == NULL) {
		if ((pip = dt_zalloc(dtp, sizeof(*pip))) == NULL)
			return -1;

		if ((pip->pi_offs = dt_zalloc(dtp,
		    sizeof(uint32_t))) == NULL) {
			dt_free(dtp, pip);
			return -1;
		}

		if ((pip->pi_enoffs = dt_zalloc(dtp,
		    sizeof(uint32_t))) == NULL) {
			dt_free(dtp, pip->pi_offs);
			dt_free(dtp, pip);
			return -1;
		}

		strlcpy(pip->pi_fname, fname, sizeof(pip->pi_fname));
		if (rname != NULL) {
			if (strlen(rname) + 1 > sizeof(pip->pi_rname)) {
				dt_free(dtp, pip->pi_offs);
				dt_free(dtp, pip);
				return dt_set_errno(dtp, EDT_COMPILER);
			}
			strcpy(pip->pi_rname, rname);
		}

		pip->pi_noffs = 0;
		pip->pi_maxoffs = 1;
		pip->pi_nenoffs = 0;
		pip->pi_maxenoffs = 1;

		pip->pi_next = prp->pr_inst;

		prp->pr_inst = pip;
	}

	if (isenabled) {
		offs = &pip->pi_enoffs;
		noffs = &pip->pi_nenoffs;
		maxoffs = &pip->pi_maxenoffs;
	} else {
		offs = &pip->pi_offs;
		noffs = &pip->pi_noffs;
		maxoffs = &pip->pi_maxoffs;
	}

	if (*noffs == *maxoffs) {
		uint_t new_max = *maxoffs * 2;
		uint32_t *new_offs = dt_calloc(dtp, new_max, sizeof(uint32_t));

		if (new_offs == NULL)
			return -1;

		memcpy(new_offs, *offs, sizeof(uint32_t) * *maxoffs);

		dt_free(dtp, *offs);
		*maxoffs = new_max;
		*offs = new_offs;
	}

	dt_dprintf("defined probe %s %s:%s %s() +0x%x (%s)\n",
	    isenabled ? "(is-enabled)" : "",
	    pvp->desc.dtvd_name, prp->pr_ident->di_name, fname, offset,
	    rname != NULL ? rname : fname);

	assert(*noffs < *maxoffs);
	(*offs)[(*noffs)++] = offset;

	return 0;
}

/*
 * Lookup the dynamic translator type tag for the specified probe argument and
 * assign the type to the specified node.  If the type is not yet defined, add
 * it to the "D" module's type container as a typedef for an unknown type.
 */
dt_node_t *
dt_probe_tag(dt_probe_t *prp, uint_t argn, dt_node_t *dnp)
{
	dtrace_hdl_t *dtp = prp->prov->pv_hdl;
	dtrace_typeinfo_t dtt;
	size_t len;
	char *tag;

	len = snprintf(NULL, 0, "__dtrace_%s___%s_arg%u",
	    prp->prov->desc.dtvd_name, prp->pr_name, argn);

	tag = alloca(len + 1);

	snprintf(tag, len + 1, "__dtrace_%s___%s_arg%u",
	    prp->prov->desc.dtvd_name, prp->pr_name, argn);

	if (dtrace_lookup_by_type(dtp, DTRACE_OBJ_DDEFS, tag, &dtt) != 0) {
		dtt.dtt_object = DTRACE_OBJ_DDEFS;
		dtt.dtt_ctfp = DT_DYN_CTFP(dtp);
		dtt.dtt_type = ctf_add_typedef(DT_DYN_CTFP(dtp),
		    CTF_ADD_ROOT, tag, DT_DYN_TYPE(dtp));

		if (dtt.dtt_type == CTF_ERR ||
		    ctf_update(dtt.dtt_ctfp) == CTF_ERR) {
			xyerror(D_UNKNOWN, "cannot define type %s: %s\n",
			    tag, ctf_errmsg(ctf_errno(dtt.dtt_ctfp)));
		}
	}

	memset(dnp, 0, sizeof(dt_node_t));
	dnp->dn_kind = DT_NODE_TYPE;

	dt_node_type_assign(dnp, dtt.dtt_ctfp, dtt.dtt_type);
	dt_node_attr_assign(dnp, _dtrace_defattr);

	return dnp;
}

dt_probe_t *
dt_probe_insert(dtrace_hdl_t *dtp, dt_provider_t *prov, const char *prv,
		const char *mod, const char *fun, const char *prb, void *datap)
{
	dt_probe_t		*prp;
	dtrace_probedesc_t	*desc;

	/* If necessary, grow the probes array. */
	if (dtp->dt_probe_id + 1 > dtp->dt_probes_sz) {
		dt_probe_t **nprobes;
		uint32_t nprobes_sz = dtp->dt_probes_sz + 1024;

		if (nprobes_sz < dtp->dt_probes_sz) {	/* overflow */
			dt_set_errno(dtp, EDT_NOMEM);
			goto err;
		}

		nprobes = dt_calloc(dtp, nprobes_sz, sizeof(dt_probe_t *));
		if (nprobes == NULL)
			goto err;

		if (dtp->dt_probes)
			memcpy(nprobes, dtp->dt_probes,
			       dtp->dt_probes_sz * sizeof(dt_probe_t *));

		free(dtp->dt_probes);
		dtp->dt_probes = nprobes;
		dtp->dt_probes_sz = nprobes_sz;
	}

	/* Allocate the new probe and fill in its basic info. */
	if ((prp = dt_zalloc(dtp, sizeof(dt_probe_t))) == NULL)
		goto err;

	if ((desc = dt_alloc(dtp, sizeof(dtrace_probedesc_t))) == NULL) {
		dt_free(dtp, prp);
		goto err;
	}

	desc->id = dtp->dt_probe_id++;
	desc->prv = strdup(prv);
	desc->mod = strdup(mod);
	desc->fun = strdup(fun);
	desc->prb = strdup(prb);

	prp->desc = desc;
	prp->prov = prov;
	prp->prv_data = datap;
	prp->argc = -1;

	dt_htab_insert(dtp->dt_byprv, prp);
	dt_htab_insert(dtp->dt_bymod, prp);
	dt_htab_insert(dtp->dt_byfun, prp);
	dt_htab_insert(dtp->dt_byprb, prp);
	dt_htab_insert(dtp->dt_byfqn, prp);

	dtp->dt_probes[dtp->dt_probe_id - 1] = prp;

	return prp;

err:
	if (datap && prov->impl->probe_destroy)
		prov->impl->probe_destroy(dtp, datap);

	return NULL;
}

static int
dt_probe_gmatch(const dt_probe_t *prp, dtrace_probedesc_t *pdp)
{
#define MATCH_ONE(nam, n)						\
	if (pdp->nam) {							\
		if (pdp->id & (1 << n)) {				\
			if (!dt_gmatch(prp->desc->nam, pdp->nam))	\
				return 0;				\
		} else if (strcmp(prp->desc->nam, pdp->nam) != 0)	\
				return 0;				\
	}

	MATCH_ONE(prv, 3)
	MATCH_ONE(mod, 2)
	MATCH_ONE(fun, 1)
	MATCH_ONE(prb, 0)

	return 1;
}

/*
 * Look for a probe that matches the probe description in 'pdp'.
 *
 * If no probe is found, this function will return NULL (dt_errno will be set).
 *
 * If a probe id is provided in the probe description, a direct lookup can be
 * performed in the probe array.
 *
 * If a probe description is provided without any glob elements, a lookup can
 * be performed on the fully qualified probe name (htab lookup in dt_byfqn).
 *
 * If a probe description is provided with a mix of exact and glob elements,
 * a htab lookup is performed on the element that is commonly known to be the
 * most restrictive (dt_byfun is usually best, then dt_byprb, then dt_bymod,
 * and finally dt_byprv).  Further matching is then done on just the probes in
 * that htab bucket.  Elements that must match exactly are compared first.
 *
 * More than one probe might match, but only one probe will be returned.
 */
dt_probe_t *
dt_probe_lookup(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp)
{
	dt_probe_t		*prp;
	dt_probe_t		tmpl;
	dtrace_probedesc_t	desc;
	int			p_is_glob, m_is_glob, f_is_glob, n_is_glob;

	/*
	 * If a probe id is provided, we can do a direct lookup.
	 */
	if (pdp->id != DTRACE_IDNONE) {
		if (pdp->id >= dtp->dt_probe_id)
			goto no_probe;

		prp = dtp->dt_probes[pdp->id];
		if (!prp)
			goto no_probe;

		return prp;
	}

	tmpl.desc = pdp;

	p_is_glob = pdp->prv[0] == '\0' || strisglob(pdp->prv);
	m_is_glob = pdp->mod[0] == '\0' || strisglob(pdp->mod);
	f_is_glob = pdp->fun[0] == '\0' || strisglob(pdp->fun);
	n_is_glob = pdp->prb[0] == '\0' || strisglob(pdp->prb);

	/*
	 * If an exact (fully qualified) probe description is provided, a
	 * simple htab lookup in dtp->dt_byfqn will suffice.
	 */
	if (p_is_glob + m_is_glob + f_is_glob + n_is_glob == 0) {
		prp = dt_htab_lookup(dtp->dt_byfqn, &tmpl);
		if (!prp)
			goto no_probe;

		return prp;
	}

	/*
	 * If at least one element is specified as a string to match exactly,
	 * use that to consult its respective htab.  If all elements are
	 * specified as glob patterns (or the empty string), we need to loop
	 * through all probes and look for a match.
	 */
	desc = *pdp;
	desc.id = (p_is_glob << 3) | (m_is_glob << 2) | (f_is_glob << 1) |
		  n_is_glob;

	if (!f_is_glob)
		prp = dt_htab_find(dtp->dt_byfun, &tmpl,
				   (dt_htab_ecmp_fn *)dt_probe_gmatch, &desc);
	else if (!n_is_glob)
		prp = dt_htab_find(dtp->dt_byprb, &tmpl,
				   (dt_htab_ecmp_fn *)dt_probe_gmatch, &desc);
	else if (!m_is_glob)
		prp = dt_htab_find(dtp->dt_bymod, &tmpl,
				   (dt_htab_ecmp_fn *)dt_probe_gmatch, &desc);
	else if (!p_is_glob)
		prp = dt_htab_find(dtp->dt_byprv, &tmpl,
				   (dt_htab_ecmp_fn *)dt_probe_gmatch, &desc);
	else {
		int			i;
		dtrace_probedesc_t	desc;

		/*
		 * To avoid checking multiple times whether an element in the
		 * probe specification is a glob pattern, we (ab)use the
		 * desc->id value (unused at this point) to store this
		 * information as a bitmap.
		 */
		desc = *pdp;
		desc.id = (p_is_glob << 3) | (m_is_glob << 2) |
			  (f_is_glob << 1) | n_is_glob;

		for (i = 0; i < dtp->dt_probe_id; i++) {
			prp = dtp->dt_probes[i];
			if (prp && dt_probe_gmatch(prp, &desc))
				break;
		}
		if (i >= dtp->dt_probe_id)
			prp = NULL;
	}

	if (prp)
		return prp;

no_probe:
	dt_set_errno(dtp, EDT_NOPROBE);
	return NULL;
}

dt_probe_t *
dt_probe_lookup_by_name(dtrace_hdl_t *dtp, const char *name)
{
	return NULL;
}

void
dt_probe_delete(dtrace_hdl_t *dtp, dt_probe_t *prp)
{
	dt_htab_delete(dtp->dt_byprv, prp);
	dt_htab_delete(dtp->dt_bymod, prp);
	dt_htab_delete(dtp->dt_byfun, prp);
	dt_htab_delete(dtp->dt_byprb, prp);
	dt_htab_delete(dtp->dt_byfqn, prp);

	dtp->dt_probes[prp->desc->id] = NULL;

	/* FIXME: Add cleanup code for the dt_probe_t itself. */
}

static int
dt_probe_args_info(dtrace_hdl_t *dtp, dt_probe_t *prp)
{
	int			argc = 0;
	dt_argdesc_t		*argv = NULL;
	int			i, nc, xc;
	dtrace_typeinfo_t	dtt;

	/* Only retrieve probe argument information once per probe. */
	if (prp->argc != -1)
		return 0;
	if (prp->prov->impl->probe_info &&
	    prp->prov->impl->probe_info(dtp, prp, &argc, &argv) == -1)
		return -1;

	if (!argc || !argv) {
		prp->argc = 0;
		return 0;
	}

	nc = 0;
	for (xc = 0; xc < argc; xc++)
		nc = MAX(nc, argv[xc].mapping);
	nc++;				/* Number of nargs = highest arg + 1 */

	/*
	 * Now that we have discovered the number of native and translated
	 * arguments from the argument descriptions, allocate nodes for the
	 * arguments and their types.
	 */
	dt_probe_alloc_args(prp, nc, xc);

	if ((xc != 0 && prp->xargs == NULL) || (nc != 0 && prp->nargs == NULL))
		return 0;

	/*
	 * Iterate over the arguments and assign their types to prp->nargv[],
	 * prp->xargv[], and record mappings in prp->mapping[].
	 */
	for (i = 0; i < argc; i++) {
		if (dtrace_type_strcompile(dtp, argv[i].native, &dtt) != 0) {
			dt_dprintf("failed to resolve input type %s for "
				   "%s:%s:%s:%s arg #%d: %s\n", argv[i].native,
				   prp->desc->prv, prp->desc->mod,
				   prp->desc->fun, prp->desc->prb, i,
				   dtrace_errmsg(dtp, dtrace_errno(dtp)));

			dtt.dtt_object = NULL;
			dtt.dtt_ctfp = NULL;
			dtt.dtt_type = CTF_ERR;
		} else {
			dt_node_type_assign(prp->nargv[argv[i].mapping],
					    dtt.dtt_ctfp, dtt.dtt_type);
		}

		if (dtt.dtt_type != CTF_ERR &&
		    (!argv[i].xlate || strcmp(argv[i].native,
					      argv[i].xlate) == 0)) {
			dt_node_type_propagate(prp->nargv[argv[i].mapping],
					       prp->xargv[i]);
		} else if (dtrace_type_strcompile(dtp, argv[i].xlate,
						  &dtt) != 0) {
			dt_dprintf("failed to resolve output type %s for "
				   "%s:%s:%s:%s arg #%d: %s\n", argv[i].xlate,
				   prp->desc->prv, prp->desc->mod,
				   prp->desc->fun, prp->desc->prb, i,
				   dtrace_errmsg(dtp, dtrace_errno(dtp)));

			dtt.dtt_object = NULL;
			dtt.dtt_ctfp = NULL;
			dtt.dtt_type = CTF_ERR;
		} else {
			dt_node_type_assign(prp->xargv[i],
					    dtt.dtt_ctfp, dtt.dtt_type);
		}

		prp->mapping[i] = argv[i].mapping;
		prp->argv[i] = dtt;
		prp->xargv[i]->dn_flags |= argv[i].flags;

		free((char *)argv[i].native);
		free((char *)argv[i].xlate);
	}

	dt_free(dtp, argv);

	return 0;
}

/*ARGSUSED*/
static int
dt_probe_first_match(dtrace_hdl_t *dtp, dt_probe_t *prp, dt_probe_t **prpp)
{
	if (*prpp == NULL) {
		*prpp = prp;
		return 0;
	}

	return 1;
}

dt_probe_t *
dt_probe_info(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp,
	      dtrace_probeinfo_t *pip)
{
	int p_is_glob = pdp->prv[0] == '\0' || strisglob(pdp->prv);
	int m_is_glob = pdp->mod[0] == '\0' || strisglob(pdp->mod);
	int f_is_glob = pdp->fun[0] == '\0' || strisglob(pdp->fun);
	int n_is_glob = pdp->prb[0] == '\0' || strisglob(pdp->prb);

	dt_probe_t *prp = NULL;
	const dtrace_pattr_t *pap;
	dt_provider_t *pvp;
	int m;

	/*
	 * Call dt_probe_iter() to find matching probes.  Our
	 * dt_probe_first_match() callback will produce the following results:
	 *
	 * m < 0 dt_probe_iter() found zero matches (or failed).
	 * m > 0 dt_probe_iter() found more than one match.
	 * m = 0 dt_probe_iter() found exactly one match.
	 */
	if ((m = dt_probe_iter(dtp, pdp, (dt_probe_f *)dt_probe_first_match, NULL, &prp)) < 0)
		return NULL; /* dt_errno is set for us */

	if ((pvp = prp->prov) == NULL)
		return NULL; /* dt_errno is set for us */

	/*
	 * If more than one probe was matched, then do not report probe
	 * information if either of the following conditions is true:
	 *
	 * (a) The Arguments Data stability of the matched provider is
	 *	less than Evolving.
	 *
	 * (b) Any description component that is at least Evolving is
	 *	empty or is specified using a globbing expression.
	 *
	 * These conditions imply that providers that provide Evolving
	 * or better Arguments Data stability must guarantee that all
	 * probes with identical field names in a field of Evolving or
	 * better Name stability have identical argument signatures.
	 */
	if (m > 0) {
		if (pvp->desc.dtvd_attr.dtpa_args.dtat_data <
		    DTRACE_STABILITY_EVOLVING && p_is_glob) {
			dt_set_errno(dtp, EDT_UNSTABLE);
			return NULL;
		}

		if (pvp->desc.dtvd_attr.dtpa_mod.dtat_name >=
		    DTRACE_STABILITY_EVOLVING && m_is_glob) {
			dt_set_errno(dtp, EDT_UNSTABLE);
			return NULL;
		}

		if (pvp->desc.dtvd_attr.dtpa_func.dtat_name >=
		    DTRACE_STABILITY_EVOLVING && f_is_glob) {
			dt_set_errno(dtp, EDT_UNSTABLE);
			return NULL;
		}

		if (pvp->desc.dtvd_attr.dtpa_name.dtat_name >=
		    DTRACE_STABILITY_EVOLVING && n_is_glob) {
			dt_set_errno(dtp, EDT_UNSTABLE);
			return NULL;
		}
	}

	/*
	 * Compute the probe description attributes by taking the minimum of
	 * the attributes of the specified fields.  If no provider is specified
	 * or a glob pattern is used for the provider, use Unstable attributes.
	 */
	if (p_is_glob)
		pap = &_dtrace_prvdesc;
	else
		pap = &pvp->desc.dtvd_attr;

	pip->dtp_attr = pap->dtpa_provider;

	if (!m_is_glob)
		pip->dtp_attr = dt_attr_min(pip->dtp_attr, pap->dtpa_mod);
	if (!f_is_glob)
		pip->dtp_attr = dt_attr_min(pip->dtp_attr, pap->dtpa_func);
	if (!n_is_glob)
		pip->dtp_attr = dt_attr_min(pip->dtp_attr, pap->dtpa_name);

	if (dt_probe_args_info(dtp, prp) == -1)
		return NULL;

	pip->dtp_arga = pap->dtpa_args;
	pip->dtp_argv = prp->argv;
	pip->dtp_argc = prp->argc;

	return prp;
}

int
dtrace_probe_info(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp,
		  dtrace_probeinfo_t *pip)
{
	return dt_probe_info(dtp, pdp, pip) != NULL ? 0
						    : -1;
}

int
dt_probe_iter(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp,
	      dt_probe_f *pfunc, dtrace_probe_f *dfunc, void *arg)
{
	dtrace_probedesc_t	desc;
	dt_probe_t		tmpl;
	dt_probe_t		*prp;
	dt_provider_t		*pvp;
	dt_htab_next_t		*it = NULL;
	int			i;
	int			p_is_glob, m_is_glob, f_is_glob, n_is_glob;
	int			rv = 0;
	int			matches = 0;

	assert(dfunc == NULL || pfunc == NULL);

	/*
	 * Special case: if no probe description is provided, we need to loop
	 * over all registered probes.
	 */
	if (!pdp) {
		for (i = 0; i < dtp->dt_probe_id; i++) {
			if (!dtp->dt_probes[i])
				continue;
			if (dfunc != NULL)
				rv = dfunc(dtp, dtp->dt_probes[i]->desc, arg);
			else if (pfunc != NULL)
				rv = pfunc(dtp, dtp->dt_probes[i], arg);

			if (rv != 0)
				return rv;

			matches++;
		}

		goto done;
	}

	/*
	 * Special case: If a probe id is provided, we can do a direct lookup.
	 */
	if (pdp->id != DTRACE_IDNONE) {
		if (pdp->id >= dtp->dt_probe_id)
			goto done;

		prp = dtp->dt_probes[pdp->id];
		if (!prp)
			goto done;

		if (dfunc != NULL)
			rv = dfunc(dtp, prp->desc, arg);
		else if (pfunc != NULL)
			rv = pfunc(dtp, prp, arg);

		if (rv != 0)
			return rv;

		matches = 1;
		goto done;
	}

	p_is_glob = pdp->prv[0] == '\0' || strisglob(pdp->prv);
	m_is_glob = pdp->mod[0] == '\0' || strisglob(pdp->mod);
	f_is_glob = pdp->fun[0] == '\0' || strisglob(pdp->fun);
	n_is_glob = pdp->prb[0] == '\0' || strisglob(pdp->prb);

	tmpl.desc = pdp;

	/*
	 * Loop over providers, allowing them to provide these probes.
	 */
	while ((pvp = dt_htab_next(dtp->dt_provs, &it)) != NULL) {
		if (pvp->impl == NULL ||
		    pvp->impl->provide == NULL ||
		    !dt_gmatch(pvp->desc.dtvd_name, pdp->prv))
			continue;
		memcpy(&desc, pdp, sizeof(desc));
		desc.prv = pvp->desc.dtvd_name;
		pvp->impl->provide(dtp, &desc);
	}

	/*
	 * Special case: if the probe is fully specified (none of the elements
	 * are empty of a glob pattern, we can do a direct lookup based on the
	 * fully qualified probe name.
	 */
	if (p_is_glob + m_is_glob + f_is_glob + n_is_glob == 0) {
		prp = dt_htab_lookup(dtp->dt_byfqn, &tmpl);
		if (!prp)
			goto done;

		if (dfunc != NULL)
			rv = dfunc(dtp, prp->desc, arg);
		else if (pfunc != NULL)
			rv = pfunc(dtp, prp, arg);

		if (rv != 0)
			return rv;

		matches = 1;
		goto done;
	}

	/*
	 * If at least one probe name specification element is an exact string
	 * to match, use the most specific one to perform a htab lookup.  The
	 * order of preference is:
	 *	function name (usually best distribution of probes in htab)
	 *	probe name
	 *	module name
	 *	provider name (usually worst distribution of probes in htab)
	 *
	 * Further matching of probes will then continue based on other exact
	 * string elements, and finally glob-pattern elements.
	 *
	 * To avoid checking multiple times whether an element in the probe
	 * specification is a glob pattern, we (ab)use the desc->id value
	 * (unused at this point) to store this information a a bitmap.
	 */
	desc = *pdp;
	desc.id = (p_is_glob << 3) | (m_is_glob << 2) | (f_is_glob << 1) |
		  n_is_glob;

#define HTAB_GMATCH(c, nam)						\
	if (!c##_is_glob) {						\
		dt_probe_t	*nxt;					\
									\
		prp = dt_htab_lookup(dtp->dt_by##nam, &tmpl);		\
		if (!prp)						\
			goto done;					\
									\
		desc.nam = NULL;					\
		do {							\
			nxt = prp->he_##nam.next;			\
			if (!dt_probe_gmatch(prp, &desc))		\
				continue;				\
									\
			if (dfunc != NULL)				\
				rv = dfunc(dtp, prp->desc, arg);	\
			else if (pfunc != NULL)				\
				rv = pfunc(dtp, prp, arg);		\
									\
			if (rv != 0)					\
				return rv;				\
									\
			matches++;					\
		} while ((prp = nxt));					\
									\
		goto done;						\
	}

	HTAB_GMATCH(f, fun)
	HTAB_GMATCH(n, prb)
	HTAB_GMATCH(m, mod)
	HTAB_GMATCH(p, prv)

	/*
	 * If all probe specification elements are glob patterns, we have no
	 * choice but to run through the entire list of probes, matching them
	 * to the given probe description, one by one.
	 */
	for (i = 0; i < dtp->dt_probe_id; i++) {
		prp = dtp->dt_probes[i];
		if (!prp)
			continue;
		if (!dt_probe_gmatch(prp, &desc))
			continue;

		if (dfunc != NULL)
			rv = dfunc(dtp, prp->desc, arg);
		else if (pfunc != NULL)
			rv = pfunc(dtp, prp, arg);

		if (rv != 0)
			return rv;

		matches++;
	}

done:
	return matches ? 0
		       : dt_set_errno(dtp, EDT_NOPROBE);
}

int
dtrace_probe_iter(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp,
		  dtrace_probe_f *func, void *arg)
{
	return dt_probe_iter(dtp, pdp, NULL, func, arg);
}

/*
 * Create an ERROR-probe specific copy of a given stmt.
 *
 * A modified copy of the stmt is necessary because the ERROR probe may share
 * some stmts with other probes, and yet it needs to be handled differently.
 *
 * In the copy of the stmt, the clause is named dt_error_N, where N is taken
 * from the original stmt's dt_clause_N (which also guarantees uniqueness).
 */
static dtrace_stmtdesc_t *
dt_probe_error_stmt(dtrace_hdl_t *dtp, dtrace_stmtdesc_t *sdp)
{
	char		*name;
	int		len;
	dt_ident_t	*idp = sdp->dtsd_clause;
	dtrace_difo_t	*dp = dt_dlib_get_func_difo(dtp, idp);
	dt_ident_t	*nidp = NULL;
	dtrace_difo_t	*ndp;
	dtrace_stmtdesc_t *nsdp = NULL;

	/*
	 * Copy the DIFO.
	 */
	ndp = dt_difo_copy(dtp, dp);
	if (ndp == NULL)
		goto no_mem;

	/*
	 * Generate a new symbol name from the given identifier.
	 */
	len = strlen(idp->di_name);
	name = dt_alloc(dtp, len);
	if (name == NULL)
		goto no_mem;
	snprintf(name, len, "dt_error_%s", idp->di_name + strlen("dt_clause_"));

	/*
	 * Add the new symbol to the BPF identifier table and associate the
	 * modified copy of the DIFO with the symbol.
	 */
	nidp = dt_dlib_add_func(dtp, name);
	dt_free(dtp, name);
	if (nidp == NULL)
		goto no_mem;

	dt_ident_set_data(nidp, ndp);

	nsdp = dt_alloc(dtp, sizeof(dtrace_stmtdesc_t));
	if (nsdp == NULL)
		goto no_mem;
	nsdp->dtsd_clause = nidp;

	return nsdp;

no_mem:
	if (ndp != NULL)
		dt_difo_free(dtp, ndp);
	if (nidp != NULL)
		dt_ident_destroy(nidp);

	dt_set_errno(dtp, EDT_NOMEM);
	return NULL;
}

int
dt_probe_add_stmt(dtrace_hdl_t *dtp, dt_probe_t *prp, dtrace_stmtdesc_t *sdp)
{
	dt_probe_stmt_t	*psp;

	psp = dt_zalloc(dtp, sizeof(dt_probe_stmt_t));
	if (psp == NULL)
		return dt_set_errno(dtp, EDT_NOMEM);

	if (prp == dtp->dt_error) {
		psp->stmt = dt_probe_error_stmt(dtp, sdp);
		if (psp->stmt == NULL) {
			dt_free(dtp, psp);
			return 0;
		}
	} else
		psp->stmt = sdp;

	dt_list_append(&prp->stmts, psp);

	return 0;
}

int
dt_probe_add_stmt_matchall(dtrace_hdl_t *dtp, dt_probe_t *prp)
{
	int	i, rc = 0;

	for (i = 0; i < dtp->dt_stmt_nextid; i++) {
		dtrace_stmtdesc_t	*sdp = dtp->dt_stmts[i];

		if (sdp == NULL)
			continue;

		if (dt_gmatch(prp->desc->prv,
			      sdp->dtsd_ecbdesc->dted_probe.prv) &&
		    dt_gmatch(prp->desc->mod,
			      sdp->dtsd_ecbdesc->dted_probe.mod) &&
		    dt_gmatch(prp->desc->fun,
			      sdp->dtsd_ecbdesc->dted_probe.fun) &&
		    dt_gmatch(prp->desc->prb,
			      sdp->dtsd_ecbdesc->dted_probe.prb)) {
			rc = dt_probe_add_stmt(dtp, prp, sdp);
			if (rc < 0)
				break;
		}
	}

	return rc;
}

int
dt_probe_stmt_iter(dtrace_hdl_t *dtp, const dt_probe_t *prp, dt_stmt_f *func, void *arg)
{
	dt_probe_stmt_t	*psp;
	int		rc;

	assert(func != NULL);

	for (psp = dt_list_next(&prp->stmts); psp != NULL;
	     psp = dt_list_next(psp)) {
		rc = func(dtp, psp->stmt, arg);

		if (rc != 0)
			return rc;
	}

	return 0;
}

int
dt_probe_add_dependent(dtrace_hdl_t *dtp, dt_probe_t *prp, dt_probe_t *dprp)
{
	dt_probe_dependent_t	*pdp;

	/* Ignore dependent probes already in the list. */
	for (pdp = dt_list_next(&prp->dependents); pdp != NULL;
	     pdp = dt_list_next(pdp)) {
		if (pdp->probe == dprp)
			return 0;
	}

	pdp = dt_zalloc(dtp, sizeof(dt_probe_dependent_t));
	if (pdp == NULL)
		return dt_set_errno(dtp, EDT_NOMEM);

	pdp->probe = dprp;

	dt_list_append(&prp->dependents, pdp);

	return 0;
}

int
dt_probe_dependent_iter(dtrace_hdl_t *dtp, const dt_probe_t *prp,
			dt_dependent_f *func, void *arg)
{
	dt_probe_dependent_t	*pdp;
	int			rc;

	assert(func != NULL);

	for (pdp = dt_list_next(&prp->dependents); pdp != NULL;
	     pdp = dt_list_next(pdp)) {
		rc = func(dtp, pdp->probe, arg);

		if (rc != 0)
			return rc;
	}

	return 0;
}

void
dt_probe_init(dtrace_hdl_t *dtp)
{
	dtp->dt_byprv = dt_htab_create(&prv_htab_ops);
	dtp->dt_bymod = dt_htab_create(&mod_htab_ops);
	dtp->dt_byfun = dt_htab_create(&fun_htab_ops);
	dtp->dt_byprb = dt_htab_create(&prb_htab_ops);
	dtp->dt_byfqn = dt_htab_create(&fqn_htab_ops);

	dtp->dt_probes = NULL;
	dtp->dt_probes_sz = 0;
	dtp->dt_probe_id = 1;
}

void
dt_probe_detach_all(dtrace_hdl_t *dtp)
{
	dt_probe_t	*prp;

	for (prp = dt_list_next(&dtp->dt_enablings); prp != NULL;
	     prp = dt_list_next(prp)) {
		if (prp->prov && prp->prov->impl && prp->prov->impl->detach)
			prp->prov->impl->detach(dtp, prp);
	}
}

void
dt_probe_fini(dtrace_hdl_t *dtp)
{
	uint32_t	i;

	for (i = 0; i < dtp->dt_probes_sz; i++) {
		dt_probe_t	*prp = dtp->dt_probes[i];

		if (prp == NULL)
			continue;

		dt_probe_destroy(prp);
	}

	dt_htab_destroy(dtp->dt_byprv);
	dt_htab_destroy(dtp->dt_bymod);
	dt_htab_destroy(dtp->dt_byfun);
	dt_htab_destroy(dtp->dt_byprb);
	dt_htab_destroy(dtp->dt_byfqn);
	dtp->dt_byprv = NULL;
	dtp->dt_bymod = NULL;
	dtp->dt_byfun = NULL;
	dtp->dt_byprb = NULL;
	dtp->dt_byfqn = NULL;

	dt_free(dtp, dtp->dt_probes);
	dtp->dt_probes = NULL;
	dtp->dt_probes_sz = 0;
	dtp->dt_probe_id = 1;
}

void
dt_probe_stats(dtrace_hdl_t *dtp)
{
	dt_htab_stats("byprv", dtp->dt_byprv);
	dt_htab_stats("bymod", dtp->dt_bymod);
	dt_htab_stats("byfun", dtp->dt_byfun);
	dt_htab_stats("byprb", dtp->dt_byprb);
	dt_htab_stats("byfqn", dtp->dt_byfqn);
}

int
dtrace_id2desc(dtrace_hdl_t *dtp, dtrace_id_t id, dtrace_probedesc_t *pdp)
{
	dt_probe_t	*prp;

	if (id >= dtp->dt_probe_id)
		return dt_set_errno(dtp, EDT_BADID);

	prp = dtp->dt_probes[id];
	if (!prp)
		return dt_set_errno(dtp, EDT_BADID);

	memcpy(pdp, prp->desc, sizeof(dtrace_probedesc_t));

        return 0;
}
