/*
 * 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 "consumer.hpp"
#include "fd-limit.hpp"
#include "health-sessiond.hpp"
#include "recording-channel-configuration.hpp"
#include "ust-app-channel.hpp"
#include "ust-app-ctx.hpp"
#include "ust-app-event.hpp"
#include "ust-app.hpp"
#include "ust-consumer.hpp"

#include <common/common.hpp>
#include <common/compat/errno.hpp>
#include <common/exception.hpp>
#include <common/format.hpp>
#include <common/make-unique.hpp>
#include <common/scope-exit.hpp>
#include <common/urcu.hpp>

#include <lttng/ust-ctl.h>
#include <lttng/ust-error.h>

#include <inttypes.h>
#include <pthread.h>

namespace lsu = lttng::sessiond::ust;
namespace lsc = lttng::sessiond::config;

namespace {
/* Next available channel key. Access under next_channel_key_lock. */
uint64_t _next_channel_key;
pthread_mutex_t next_channel_key_lock = PTHREAD_MUTEX_INITIALIZER;

/*
 * Return the incremented value of next_channel_key.
 */
uint64_t get_next_channel_key()
{
	uint64_t ret;

	pthread_mutex_lock(&next_channel_key_lock);
	ret = ++_next_channel_key;
	pthread_mutex_unlock(&next_channel_key_lock);
	return ret;
}

} /* namespace */

/* -- app_channel -- */

lsu::app_channel::app_channel(lsu::app_session& session_,
			      const lsc::channel_configuration& channel_config_) :
	enabled(channel_config_.channel_type() == lsc::channel_configuration::channel_type_t::DATA ?
			static_cast<const lsc::recording_channel_configuration&>(channel_config_)
				.is_enabled :
			true),
	handle(-1),
	key(get_next_channel_key()),
	session(session_),
	channel_config(channel_config_)
{
	DBG3("UST app channel %s allocated", channel_config.name.c_str());
}

lsu::app_channel::~app_channel()
{
	DBG3("UST app deleting channel %s", channel_config.name.c_str());

	/*
	 * Wipe streams first. Stream destructors access the app's
	 * command socket, which must still be reachable at this point.
	 */
	streams.clear();

	/* Wipe contexts. Destructors release UST objects. */
	contexts.clear();

	/* Wipe events. Destructors release UST tracer-side objects. */
	events.clear();

	if (!obj.get()) {
		return;
	}

	/* Deregister objd from the app's registry via RAII token. */
	objd_token.reset();

	/*
	 * Notify the tracer; the wrapper destructor handles the local
	 * cleanup pass (close `wakeup_fd`) and frees the object. The
	 * channel's `wakeup_fd` was kept open since the SCM_RIGHTS send
	 * (see `send_to_app_per_pid()`) so this is the moment the
	 * consumerd's channel-teardown POLLHUP is delivered.
	 */
	lsu::release_object_via_app(session.app(), *obj.get(), "channel");
	lttng_fd_put(LTTNG_FD_APPS, 1);
}

void lsu::app_channel::enable()
{
	health_code_update();
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	auto& app = session.app();

	app.command_socket.lock().enable(obj.get());

	enabled = true;

	DBG2("UST app channel %s enabled successfully for app: pid = %d",
	     channel_config.name.c_str(),
	     app.pid);
}

void lsu::app_channel::disable()
{
	health_code_update();
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	auto& app = session.app();

	app.command_socket.lock().disable(obj.get());

	enabled = false;

	DBG2("UST app channel %s disabled successfully for app: pid = %d",
	     channel_config.name.c_str(),
	     app.pid);
}

void lsu::app_channel::create_context(const lsc::context_configuration& context_config,
				      lttng_ust_context_attr *uctx)
{
	DBG2("UST app adding context to channel %s", channel_config.name.c_str());

	if (contexts.find(&context_config) != contexts.end()) {
		return;
	}

	auto app_context = lttng::make_unique<lsu::app_context>(*this, context_config);
	auto& app = session.app();

	health_code_update();
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	{
		lttng_ust_abi_object_data *raw = nullptr;
		app.command_socket.lock().add_context(uctx, obj.get(), &raw);
		app_context->obj = lsu::ust_object_data(raw);
	}

	if (!app_context->obj.get()) {
		LTTNG_THROW_ERROR(lttng::format(
			"Failed to add UST context: no context object returned: channel_name=`{}`, pid={}, "
			"sock={}",
			channel_config.name,
			app.pid,
			app.command_socket.fd()));
	}

	app_context->handle = app_context->obj.get()->header.handle;

	DBG2("UST app context handle %d created successfully for channel %s",
	     app_context->handle,
	     channel_config.name.c_str());

	contexts.emplace(&context_config, std::move(app_context));
}

void lsu::app_channel::send_to_app_per_pid()
{
	auto& app = session.app();

	health_code_update();

	DBG("UST app sending channel %s to UST app sock %d",
	    channel_config.name.c_str(),
	    app.command_socket.fd());

	/* Send channel to the application. */
	const auto channel_send_ret = ust_consumer_send_channel_to_ust(&app, &session, this);
	if (channel_send_ret < 0) {
		LTTNG_THROW_ERROR(lttng::format(
			"Failed to send UST channel to application: channel_name=`{}`, pid={}, sock={}, "
			"session_id={}, channel_key={}, status={}",
			channel_config.name,
			app.pid,
			app.command_socket.fd(),
			session.recording_session_id,
			key,
			channel_send_ret));
	}

	/*
	 * The channel's `wakeup_fd` has to outlive this scope: it is the
	 * sessiond's hold on the consumerd-side channel pipe, and POLLHUP
	 * triggers the destruction of the channel on the consumerd's end. The
	 * destructor closes it together with the RELEASE notification.
	 *
	 * See `ust_object_data::release_local_fds()` for the per-type rules.
	 */

	health_code_update();

	/* Send all streams to application. */
	for (auto& stream_ptr : streams) {
		const auto stream_send_ret =
			ust_consumer_send_stream_to_ust(&app, this, stream_ptr.get());
		if (stream_send_ret < 0) {
			LTTNG_THROW_ERROR(lttng::format(
				"Failed to send UST stream to application: channel_name=`{}`, pid={}, "
				"sock={}, session_id={}, channel_key={}, status={}",
				channel_config.name,
				app.pid,
				app.command_socket.fd(),
				session.recording_session_id,
				key,
				stream_send_ret));
		}

		/*
		 * The stream has been sent to the tracer; discard the local
		 * resources without sending a release command to the tracer
		 * (it now owns the handle).
		 */
		stream_ptr->discard_locally();
	}

	streams.clear();
}

void lsu::app_channel::send_to_app_per_uid(lsu::stream_group& stream_group)
{
	auto& app = session.app();

	DBG("UST app sending stream group channel to ust sock %d", app.command_socket.fd());

	/* Duplicate the master channel object for this application. */
	{
		try {
			obj = stream_group.duplicate_channel_object();
		} catch (const std::exception& ex) {
			LTTNG_THROW_ERROR(lttng::format(
				"Failed to duplicate UST channel object for per-UID send: channel_name=`{}`, "
				"pid={}, sock={}, session_id={}, channel_key={}, error={}",
				channel_config.name,
				app.pid,
				app.command_socket.fd(),
				session.recording_session_id,
				key,
				ex.what()));
		}

		handle = obj.get()->header.handle;
	}

	/* Send channel to the application. */
	const auto channel_send_ret = ust_consumer_send_channel_to_ust(&app, &session, this);
	if (channel_send_ret < 0) {
		LTTNG_THROW_ERROR(lttng::format(
			"Failed to send per-UID UST channel to application: channel_name=`{}`, pid={}, "
			"sock={}, session_id={}, channel_key={}, status={}",
			channel_config.name,
			app.pid,
			app.command_socket.fd(),
			session.recording_session_id,
			key,
			channel_send_ret));
	}

	/* See send_to_app_per_pid: the channel's wakeup_fd stays open. */

	health_code_update();

	/* Send all streams to application by duplicating from the stream group. */
	for (const auto& stream_ptr : stream_group.streams()) {
		lsu::app_stream tmp_stream(*this);

		try {
			tmp_stream.obj = stream_ptr->handle.duplicate();
		} catch (const std::exception& ex) {
			LTTNG_THROW_ERROR(lttng::format(
				"Failed to duplicate UST stream object for per-UID send: channel_name=`{}`, "
				"pid={}, sock={}, session_id={}, channel_key={}, error={}",
				channel_config.name,
				app.pid,
				app.command_socket.fd(),
				session.recording_session_id,
				key,
				ex.what()));
		}

		tmp_stream.handle = tmp_stream.obj.get()->header.handle;

		const auto stream_send_ret =
			ust_consumer_send_stream_to_ust(&app, this, &tmp_stream);
		if (stream_send_ret < 0) {
			/*
			 * discard_locally releases the local resources without
			 * notifying the tracer (the send may have failed).
			 * The destructor will then be a no-op.
			 */
			tmp_stream.discard_locally();
			LTTNG_THROW_ERROR(lttng::format(
				"Failed to send per-UID UST stream to application: channel_name=`{}`, "
				"pid={}, sock={}, session_id={}, channel_key={}, status={}",
				channel_config.name,
				app.pid,
				app.command_socket.fd(),
				session.recording_session_id,
				key,
				stream_send_ret));
		}

		/*
		 * The stream was sent successfully. Release local resources
		 * without sending a release command to the tracer.
		 */
		tmp_stream.discard_locally();
	}
}

/* -- app_stream -- */

lsu::app_stream::app_stream(lsu::app_channel& channel) : _channel(channel)
{
}

lsu::app_stream::app_stream(app_stream&& other) noexcept :
	handle(other.handle), obj(std::move(other.obj)), _channel(other._channel)
{
	other.handle = -1;
}

lsu::app_stream::~app_stream()
{
	if (!obj.get()) {
		return;
	}

	lsu::release_object_via_app(_channel.session.app(), *obj.get(), "stream");
	lttng_fd_put(LTTNG_FD_APPS, 2);
}

lsu::ust_object_data lsu::app_stream::release_obj() noexcept
{
	return std::move(obj);
}

void lsu::app_stream::discard_locally() noexcept
{
	if (!obj.get()) {
		return;
	}

	obj = lsu::ust_object_data();
	lttng_fd_put(LTTNG_FD_APPS, 2);
}

/* -- channel allocation/deallocation -- */

lsc::recording_channel_configuration::buffer_allocation_policy_t
ust_channel_type_to_allocation_policy(enum lttng_ust_abi_chan_type type)
{
	switch (type) {
	case LTTNG_UST_ABI_CHAN_PER_CPU:
		return lttng::sessiond::config::recording_channel_configuration::
			buffer_allocation_policy_t::PER_CPU;
	case LTTNG_UST_ABI_CHAN_METADATA:
		/* fall-through  */
	case LTTNG_UST_ABI_CHAN_PER_CHANNEL:
		return lttng::sessiond::config::recording_channel_configuration::
			buffer_allocation_policy_t::PER_CHANNEL;
	default:
		abort();
	}
}

enum lttng_ust_abi_chan_type allocation_policy_to_ust_channel_type(
	lsc::recording_channel_configuration::buffer_allocation_policy_t policy)
{
	namespace lsc = lttng::sessiond::config;

	switch (policy) {
	case lsc::recording_channel_configuration::buffer_allocation_policy_t::PER_CPU:
		return LTTNG_UST_ABI_CHAN_PER_CPU;
	case lsc::recording_channel_configuration::buffer_allocation_policy_t::PER_CHANNEL:
		return LTTNG_UST_ABI_CHAN_PER_CHANNEL;
	default:
		abort();
	}
}

/*
 * Ask the consumer to create a channel and get it if successful.
 *
 * Called with UST app session lock held.
 *
 * Return 0 on success or else a negative value.
 */
int do_consumer_create_channel(struct consumer_output *consumer,
			       lsu::app_session *ua_sess,
			       lsu::app_channel *ua_chan,
			       int bitness,
			       lsu::trace_class *registry,
			       struct lttng_trace_chunk *current_trace_chunk,
			       enum lttng_trace_format trace_format,
			       unsigned int output_traces,
			       unsigned int live_timer_interval)
{
	int ret;
	unsigned int nb_fd = 0;
	struct consumer_socket *socket;

	LTTNG_ASSERT(consumer);
	LTTNG_ASSERT(ua_sess);
	LTTNG_ASSERT(ua_chan);
	LTTNG_ASSERT(registry);

	const lttng::urcu::read_lock_guard read_lock;
	health_code_update();

	/* Get the right consumer socket for the application. */
	socket = consumer_find_socket_by_bitness(bitness, consumer);
	if (!socket) {
		ret = -EINVAL;
		goto error;
	}

	health_code_update();

	/* Need one fd for the channel. */
	ret = lttng_fd_get(LTTNG_FD_APPS, 1);
	if (ret < 0) {
		ERR("Exhausted number of available FD upon create channel");
		goto error;
	}

	/*
	 * Ask consumer to create channel. The consumer will return the number of
	 * stream we have to expect.
	 */
	ret = ust_consumer_ask_channel(ua_sess,
				       ua_chan,
				       consumer,
				       socket,
				       registry,
				       current_trace_chunk,
				       trace_format,
				       output_traces,
				       live_timer_interval);
	if (ret < 0) {
		goto error_ask;
	}

	/*
	 * Compute the number of fd needed before receiving them. It must be 2 per
	 * stream (2 being the default value here).
	 */
	nb_fd = DEFAULT_UST_STREAM_FD_NUM * ua_chan->expected_stream_count;

	/* Reserve the amount of file descriptor we need. */
	ret = lttng_fd_get(LTTNG_FD_APPS, nb_fd);
	if (ret < 0) {
		ERR("Exhausted number of available FD upon create channel");
		goto error_fd_get_stream;
	}

	health_code_update();

	/*
	 * Now get the channel from the consumer. This call will populate the stream
	 * vector of that channel and set the ust objects.
	 */
	if (consumer->enabled) {
		ret = ust_consumer_get_channel(socket, ua_chan);
		if (ret < 0) {
			goto error_destroy;
		}
	}

	return 0;

error_destroy:
	lttng_fd_put(LTTNG_FD_APPS, nb_fd);
error_fd_get_stream:
	/*
	 * Initiate a destroy channel on the consumer since we had an error
	 * handling it on our side. The return value is of no importance since we
	 * already have a ret value set by the previous error that we need to
	 * return.
	 */
	(void) ust_consumer_destroy_channel(socket, ua_chan);
error_ask:
	lttng_fd_put(LTTNG_FD_APPS, 1);
error:
	health_code_update();
	return ret;
}
