/*
 * SPDX-FileCopyrightText: 2011 EfficiOS Inc.
 * SPDX-FileCopyrightText: 2016 Jérémie Galarneau <jeremie.galarneau@efficios.com>
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 */

#define _LGPL_SOURCE
#include "agent.hpp"
#include "channel.hpp"
#include "event.hpp"
#include "lttng-sessiond.hpp"
#include "session.hpp"
#include "trace-ust.hpp"
#include "ust-domain-orchestrator.hpp"
#include "utils.hpp"

#include <common/bytecode/bytecode.hpp>
#include <common/compat/errno.hpp>
#include <common/context.hpp>
#include <common/error.hpp>
#include <common/filter.hpp>
#include <common/format.hpp>
#include <common/sessiond-comm/sessiond-comm.hpp>
#include <common/urcu.hpp>

#include <lttng/condition/condition.h>
#include <lttng/condition/event-rule-matches.h>
#include <lttng/event-rule/event-rule-internal.hpp>
#include <lttng/event-rule/event-rule.h>
#include <lttng/lttng.h>

#include <exception>
#include <string.h>
#include <urcu/list.h>

namespace {
void agent_enable_all(struct agent *agt)
{
	for (auto *aevent :
	     lttng::urcu::lfht_iteration_adapter<agent_event,
						 decltype(agent_event::node),
						 &agent_event::node>(*agt->events->ht)) {
		aevent->enabled_count++;
	}
}
} /* namespace */

/*
 * Enable all agent event for a given UST session.
 *
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
int event_agent_enable_all(
	std::uint64_t session_id,
	struct agent *agt,
	struct lttng_event *event,
	struct lttng_bytecode *filter,
	char *filter_expression,
	const lttng::sessiond::config::event_rule_configuration *ust_event_rule_config)
{
	int ret;

	DBG("Event agent enabling ALL events for session %" PRIu64, session_id);

	/* Enable event on agent application through TCP socket. */
	ret = event_agent_enable(
		session_id, agt, event, filter, filter_expression, ust_event_rule_config);
	if (ret != LTTNG_OK) {
		goto error;
	}

	agent_enable_all(agt);

	ret = LTTNG_OK;

error:
	return ret;
}

/*
 * Check if this event's filter requires the activation of application contexts
 * and enable them in the agent.
 * TODO: bytecode iterator does not support non-legacy application
 * contexts yet. Not an issue for now, since they are not generated by
 * the lttng-ctl library.
 */
namespace {
int add_filter_app_ctx(struct lttng_bytecode *bytecode,
		       const char *filter_expression,
		       struct agent *agt)
{
	int ret = LTTNG_OK;
	char *provider_name = nullptr, *ctx_name = nullptr;
	struct bytecode_symbol_iterator *it = bytecode_symbol_iterator_create(bytecode);

	if (!it) {
		ret = LTTNG_ERR_NOMEM;
		goto end;
	}

	do {
		struct lttng_event_context ctx;
		const char *symbol_name = bytecode_symbol_iterator_get_name(it);

		if (parse_application_context(symbol_name, &provider_name, &ctx_name)) {
			/* Not an application context. */
			continue;
		}

		ctx.ctx = LTTNG_EVENT_CONTEXT_APP_CONTEXT;
		ctx.u.app_ctx.provider_name = provider_name;
		ctx.u.app_ctx.ctx_name = ctx_name;

		/* Recognized an application context. */
		DBG("Enabling event with filter expression \"%s\" requires enabling the %s:%s application context.",
		    filter_expression,
		    provider_name,
		    ctx_name);

		ret = agent_add_context(&ctx, agt);
		if (ret != LTTNG_OK) {
			ERR("Failed to add application context %s:%s.", provider_name, ctx_name);
			goto end;
		}

		ret = agent_enable_context(&ctx, agt->domain);
		if (ret != LTTNG_OK) {
			ERR("Failed to enable application context %s:%s.", provider_name, ctx_name);
			goto end;
		}

		free(provider_name);
		free(ctx_name);
		provider_name = ctx_name = nullptr;
	} while (bytecode_symbol_iterator_next(it) == 0);
end:
	free(provider_name);
	free(ctx_name);
	bytecode_symbol_iterator_destroy(it);
	return ret;
}
} /* namespace */

namespace {
int agent_enable(struct agent *agt,
		 struct lttng_event *event,
		 struct lttng_bytecode *filter,
		 char *filter_expression,
		 const lttng::sessiond::config::event_rule_configuration *ust_event_rule_config)
{
	int ret, created = 0;
	struct agent_event *aevent;

	LTTNG_ASSERT(event);
	LTTNG_ASSERT(agt);

	const lttng::urcu::read_lock_guard read_lock;
	aevent = agent_find_event(
		event->name, event->loglevel_type, event->loglevel, filter_expression, agt);
	if (!aevent) {
		aevent = agent_create_event(event->name,
					    event->loglevel_type,
					    event->loglevel,
					    filter,
					    filter_expression);
		if (!aevent) {
			ret = LTTNG_ERR_NOMEM;
			goto error;
		}
		filter = nullptr;
		filter_expression = nullptr;
		created = 1;
		LTTNG_ASSERT(!AGENT_EVENT_IS_ENABLED(aevent));
	}

	if (created && aevent->filter) {
		ret = add_filter_app_ctx(aevent->filter, aevent->filter_expression, agt);
		if (ret != LTTNG_OK) {
			goto error;
		}
	}

	/* Already enabled? */
	if (AGENT_EVENT_IS_ENABLED(aevent)) {
		ret = LTTNG_OK;
		goto end;
	}

	ret = agent_enable_event(aevent, agt->domain);
	if (ret != LTTNG_OK) {
		goto error;
	}

	aevent->ust_event_rule_config = ust_event_rule_config;

	/* If the event was created prior to the enable, add it to the domain. */
	if (created) {
		try {
			agent_add_event(aevent, agt);
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to add agent event to domain, destroying it: event_name=`{}`, error={}",
				event->name,
				ex.what());
			agent_destroy_event(aevent);
			throw;
		}
	}

	ret = LTTNG_OK;
	goto end;

error:
	if (created) {
		agent_destroy_event(aevent);
	}
end:
	free(filter);
	free(filter_expression);
	return ret;
}
} /* namespace */

/*
 * Enable a single agent event for a given UST session.
 *
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
int event_agent_enable(
	std::uint64_t session_id,
	struct agent *agt,
	struct lttng_event *event,
	struct lttng_bytecode *filter,
	char *filter_expression,
	const lttng::sessiond::config::event_rule_configuration *ust_event_rule_config)
{
	LTTNG_ASSERT(event);
	LTTNG_ASSERT(agt);

	DBG("Enabling agent event: event pattern = '%s', session id = %" PRIu64
	    ", loglevel type = %d, loglevel = %d, filter expression = '%s'",
	    event->name,
	    session_id,
	    event->loglevel_type,
	    event->loglevel,
	    filter_expression ? filter_expression : "(none)");

	return agent_enable(agt, event, filter, filter_expression, ust_event_rule_config);
}

/*
 * Enable a single agent event for a trigger.
 *
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
int trigger_agent_enable(const struct lttng_trigger *trigger, struct agent *agt)
{
	int ret;
	enum lttng_condition_status c_status;
	enum lttng_trigger_status t_status;
	enum lttng_domain_type d_type;
	const struct lttng_condition *condition;
	const struct lttng_event_rule *rule;
	const char *filter_expression;
	char *filter_expression_copy = nullptr;
	const struct lttng_bytecode *filter_bytecode;
	struct lttng_bytecode *filter_bytecode_copy = nullptr;
	struct lttng_event *event = nullptr;
	uid_t trigger_owner_uid = 0;
	const char *trigger_name;

	LTTNG_ASSERT(trigger);
	LTTNG_ASSERT(agt);

	t_status = lttng_trigger_get_name(trigger, &trigger_name);
	if (t_status != LTTNG_TRIGGER_STATUS_OK) {
		trigger_name = "(anonymous)";
	}

	t_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner_uid);
	LTTNG_ASSERT(t_status == LTTNG_TRIGGER_STATUS_OK);

	condition = lttng_trigger_get_const_condition(trigger);

	LTTNG_ASSERT(lttng_condition_get_type(condition) ==
		     LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES);

	c_status = lttng_condition_event_rule_matches_get_rule(condition, &rule);
	LTTNG_ASSERT(c_status == LTTNG_CONDITION_STATUS_OK);

	switch (lttng_event_rule_get_type(rule)) {
	case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING:
	case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING:
	case LTTNG_EVENT_RULE_TYPE_LOG4J2_LOGGING:
	case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING:
		break;
	default:
		abort();
		break;
	}

	d_type = lttng_event_rule_get_domain_type(rule);
	LTTNG_ASSERT(d_type == agt->domain);

	event = lttng_event_rule_generate_lttng_event(rule);
	if (!event) {
		ret = LTTNG_ERR_NOMEM;
		goto end;
	}

	/* Get the internal filter expression and bytecode. */
	filter_expression = lttng_event_rule_get_internal_filter_expression(rule);
	if (filter_expression) {
		filter_expression_copy = strdup(filter_expression);
		if (!filter_expression_copy) {
			ret = LTTNG_ERR_NOMEM;
			goto end;
		}

		/* Get the filter bytecode */
		filter_bytecode = lttng_event_rule_get_filter_bytecode(rule);
		if (filter_bytecode) {
			filter_bytecode_copy = lttng_bytecode_copy(filter_bytecode);
			if (!filter_bytecode_copy) {
				ret = LTTNG_ERR_NOMEM;
				goto end;
			}
		}
	}

	DBG("Enabling agent event from trigger: trigger name = '%s', trigger owner uid = %d, token = %" PRIu64,
	    trigger_name,
	    trigger_owner_uid,
	    lttng_trigger_get_tracer_token(trigger));

	ret = agent_enable(agt, event, filter_bytecode_copy, filter_expression_copy, nullptr);
	/* Ownership was passed even in case of error. */
	filter_expression_copy = nullptr;
	filter_bytecode_copy = nullptr;

end:
	free(filter_expression_copy);
	free(filter_bytecode_copy);
	lttng_event_destroy(event);
	return ret;
}

/*
 * Return the default event name associated with the provided UST domain. Return
 * NULL on error.
 */
const char *event_get_default_agent_ust_name(enum lttng_domain_type domain)
{
	const char *default_event_name = nullptr;

	switch (domain) {
	case LTTNG_DOMAIN_LOG4J:
		default_event_name = DEFAULT_LOG4J_EVENT_NAME;
		break;
	case LTTNG_DOMAIN_LOG4J2:
		default_event_name = DEFAULT_LOG4J2_EVENT_NAME;
		break;
	case LTTNG_DOMAIN_JUL:
		default_event_name = DEFAULT_JUL_EVENT_NAME;
		break;
	case LTTNG_DOMAIN_PYTHON:
		default_event_name = DEFAULT_PYTHON_EVENT_NAME;
		break;
	default:
		abort();
	}

	return default_event_name;
}

namespace {
int trigger_agent_disable_one(const struct lttng_trigger *trigger,
			      struct agent *agt,
			      struct agent_event *aevent)

{
	int ret;

	LTTNG_ASSERT(agt);
	LTTNG_ASSERT(trigger);
	LTTNG_ASSERT(aevent);

	/*
	 * Actual ust event un-registration happens on the trigger
	 * un-registration at that point.
	 */

	DBG("Event agent disabling %s (loglevel type %d, loglevel value %d) for trigger %" PRIu64,
	    aevent->name,
	    aevent->loglevel_type,
	    aevent->loglevel_value,
	    lttng_trigger_get_tracer_token(trigger));

	/* Already disabled? */
	if (!AGENT_EVENT_IS_ENABLED(aevent)) {
		goto end;
	}

	ret = agent_disable_event(aevent, agt->domain);
	if (ret != LTTNG_OK) {
		goto error;
	}

end:
	return LTTNG_OK;

error:
	return ret;
}
} /* namespace */

/*
 * Disable a given agent event for a given UST session.
 *
 * Must be called with the RCU read lock held.
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
namespace {
int event_agent_disable_one(lttng::sessiond::ust::domain_orchestrator& orchestrator,
			    struct agent *agt,
			    struct agent_event *aevent)
{
	int ret;
	const char *ust_channel_name;

	LTTNG_ASSERT(agt);
	LTTNG_ASSERT(aevent);

	DBG("Event agent disabling %s (loglevel type %d, loglevel value %d) for session %" PRIu64,
	    aevent->name,
	    aevent->loglevel_type,
	    aevent->loglevel_value,
	    orchestrator.recording_session().id);

	/* Already disabled? */
	if (!AGENT_EVENT_IS_ENABLED(aevent)) {
		goto end;
	}

	if (agt->domain == LTTNG_DOMAIN_JUL) {
		ust_channel_name = DEFAULT_JUL_CHANNEL_NAME;
	} else if (agt->domain == LTTNG_DOMAIN_LOG4J) {
		ust_channel_name = DEFAULT_LOG4J_CHANNEL_NAME;
	} else if (agt->domain == LTTNG_DOMAIN_LOG4J2) {
		ust_channel_name = DEFAULT_LOG4J2_CHANNEL_NAME;
	} else if (agt->domain == LTTNG_DOMAIN_PYTHON) {
		ust_channel_name = DEFAULT_PYTHON_CHANNEL_NAME;
	} else {
		ret = LTTNG_ERR_INVALID;
		goto error;
	}

	LTTNG_ASSERT(aevent->ust_event_rule_config);

	try {
		const auto& channel_config = orchestrator.recording_session()
						     .get_domain(lttng::domain_class::USER_SPACE)
						     .get_channel(ust_channel_name);

		orchestrator.disable_event(channel_config, *aevent->ust_event_rule_config);
	} catch (const lttng::ctl::error& ex) {
		ERR_FMT("Failed to disable agent event on UST orchestrator: event_name=`{}`, session_id={}, error_code={}, error='{}'",
			aevent->name,
			orchestrator.recording_session().id,
			static_cast<int>(ex.code()),
			ex.what());
		ret = LTTNG_ERR_UST_DISABLE_FAIL;
		goto error;
	} catch (const std::exception& ex) {
		ERR_FMT("Failed to disable agent event on UST orchestrator: event_name=`{}`, session_id={}, error='{}'",
			aevent->name,
			orchestrator.recording_session().id,
			ex.what());
		ret = LTTNG_ERR_UST_DISABLE_FAIL;
		goto error;
	}

	/*
	 * Disable the event rule in the UST domain's channel configuration so
	 * that the per-app event synchronization path, which reads the enabled
	 * state from the configuration, sees the event as disabled.
	 *
	 * agent_event stores the configuration as a const non-owning pointer
	 * because the agent side only reads it; disable() here writes state
	 * owned by the UST-domain configuration, which is the authoritative
	 * copy.
	 */
	/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) */
	const_cast<lttng::sessiond::config::event_rule_configuration *>(
		aevent->ust_event_rule_config)
		->disable();

	ret = agent_disable_event(aevent, agt->domain);
	if (ret != LTTNG_OK) {
		goto error;
	}

end:
	return LTTNG_OK;

error:
	return ret;
}
} /* namespace */

/*
 * Disable agent event matching a given trigger.
 *
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
int trigger_agent_disable(const struct lttng_trigger *trigger, struct agent *agt)
{
	int ret = LTTNG_OK;
	struct agent_event *aevent;

	LTTNG_ASSERT(trigger);
	LTTNG_ASSERT(agt);

	DBG("Event agent disabling for trigger %" PRIu64, lttng_trigger_get_tracer_token(trigger));

	const lttng::urcu::read_lock_guard read_lock;
	aevent = agent_find_event_by_trigger(trigger, agt);

	if (aevent == nullptr) {
		DBG2("Event agent NOT found by trigger %" PRIu64,
		     lttng_trigger_get_tracer_token(trigger));
		ret = LTTNG_ERR_UST_EVENT_NOT_FOUND;
		goto end;
	}

	ret = trigger_agent_disable_one(trigger, agt, aevent);

	if (ret != LTTNG_OK) {
		goto end;
	}

end:
	return ret;
}

/*
 * Disable all agent events matching a given name for a given UST session.
 *
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
int event_agent_disable(lttng::sessiond::ust::domain_orchestrator& orchestrator,
			struct agent *agt,
			const char *event_name)
{
	int ret = LTTNG_OK;
	struct agent_event *aevent;
	struct lttng_ht_iter iter;
	struct lttng_ht_node_str *node;

	LTTNG_ASSERT(agt);
	LTTNG_ASSERT(event_name);

	DBG("Event agent disabling %s (all loglevels) for session %" PRIu64,
	    event_name,
	    orchestrator.recording_session().id);

	const lttng::urcu::read_lock_guard read_lock;
	agent_find_events_by_name(event_name, agt, &iter);
	node = lttng_ht_iter_get_node<lttng_ht_node_str>(&iter);

	if (node == nullptr) {
		DBG2("Event agent NOT found by name %s", event_name);
		ret = LTTNG_ERR_UST_EVENT_NOT_FOUND;
		goto end;
	}

	do {
		aevent = lttng::utils::container_of(node, &agent_event::node);
		ret = event_agent_disable_one(orchestrator, agt, aevent);

		if (ret != LTTNG_OK) {
			goto end;
		}

		/* Get next duplicate agent event by name. */
		agent_event_next_duplicate(event_name, agt, &iter);
		node = lttng_ht_iter_get_node<lttng_ht_node_str>(&iter);
	} while (node);
end:
	return ret;
}
/*
 * Disable all agent event for a given UST session.
 *
 * Return LTTNG_OK on success or else a LTTNG_ERR* code.
 */
int event_agent_disable_all(lttng::sessiond::ust::domain_orchestrator& orchestrator,
			    struct agent *agt)
{
	int ret;

	LTTNG_ASSERT(agt);

	/*
	 * Disable event on agent application. Continue to disable all other events
	 * if the * event is not found.
	 */
	ret = event_agent_disable(orchestrator, agt, "*");
	if (ret != LTTNG_OK && ret != LTTNG_ERR_UST_EVENT_NOT_FOUND) {
		goto error;
	}

	/* Disable every event. */
	for (auto *aevent :
	     lttng::urcu::lfht_iteration_adapter<agent_event,
						 decltype(agent_event::node),
						 &agent_event::node>(*agt->events->ht)) {
		if (!AGENT_EVENT_IS_ENABLED(aevent)) {
			continue;
		}

		ret = event_agent_disable(orchestrator, agt, aevent->name);
		if (ret != LTTNG_OK) {
			goto error_unlock;
		}
	}

	ret = LTTNG_OK;

error_unlock:
error:
	return ret;
}
