#!/bin/bash
#
# SPDX-FileCopyrightText: 2013 Christian Babeux <christian.babeux@efficios.com>
# Copyright (C) 2015-2018 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
#
# SPDX-License-Identifier: GPL-2.0-only

TEST_DESC="LTTng - Event tracker test"

CURDIR=$(dirname "$0")/
TESTDIR="$CURDIR/../../.."
TESTAPP_PATH="$TESTDIR/utils/testapp"
TESTAPP_NAME="gen-ust-events"
TESTAPP_KERNEL_NAME="gen-kernel-test-events"
TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME"
TESTAPP_KERNEL_BIN="$TESTAPP_PATH/$TESTAPP_KERNEL_NAME/$TESTAPP_KERNEL_NAME"
SESSION_NAME="tracker"
NR_ITER=1
NUM_GLOBAL_TESTS=2
NUM_UST_TESTS=283
NUM_KERNEL_TESTS=462
NUM_TESTS=$((NUM_UST_TESTS+NUM_KERNEL_TESTS+NUM_GLOBAL_TESTS))

NR_USEC_WAIT=0	#for UST gen events

SCRIPT_UID="$(id -u)"
SCRIPT_GID="$(id -g)"
SCRIPT_USERNAME="$(id -un)"
SCRIPT_GROUPNAME="$(id -gn)"

CHILD_PID=-1
WAIT_PATH=
TOUCH_BEFORE_LAST_PATH=
SYNC_BEFORE_LAST_PATH=

source $TESTDIR/utils/utils.sh

# Launch the testapp and execute it up until right before the last event. It is
# useful to do it in two seperate steps in order to test tracking and
# untracking on an active app.
function prepare_ust_app
{
	TOUCH_BEFORE_LAST_PATH=$(mktemp -u -t tmp.${FUNCNAME[0]}_touch_before_last.XXXXXX)
	SYNC_BEFORE_LAST_PATH=$(mktemp -u -t tmp.${FUNCNAME[0]}_sync_before_last.XXXXXX)

	$TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT \
		--sync-before-last-event-touch "$TOUCH_BEFORE_LAST_PATH" \
		--sync-before-last-event "$SYNC_BEFORE_LAST_PATH" &

	CHILD_PID=$!

	# Wait for the app to execute all the way to right before the last
	# event.
	while [ ! -f "${TOUCH_BEFORE_LAST_PATH}" ]; do
		sleep 0.01
	done
}

# Generate the last event.
function trace_ust_app
{
	# Ask the test app to generate the last event.
	touch "$SYNC_BEFORE_LAST_PATH"
	wait "$CHILD_PID"
	ok $? "Traced application stopped."
	rm "$SYNC_BEFORE_LAST_PATH"
	rm "$TOUCH_BEFORE_LAST_PATH"
}

function prepare_kernel_app
{
	WAIT_PATH=$(mktemp -u -t "tmp.${FUNCNAME[0]}_wait_path.XXXXXX")

	"$TESTAPP_KERNEL_BIN" "$WAIT_PATH" $NR_ITER &
	CHILD_PID=$!
}

function trace_kernel_app
{
	touch "$WAIT_PATH"
	wait "$CHILD_PID"
	ok $? "Traced application stopped."
	rm "$WAIT_PATH"
}

function test_event_tracker()
{
	local trace_path
	local domain="$1"
	local expect_event="$2"
	local wildcard="$3"
	local tracker="$4"
	local channel=''

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"

	if [ "$domain" = kernel ]; then
		channel=chan
		lttng_enable_kernel_channel 1 0 $SESSION_NAME $channel "--subbuf-size=8M --num-subbuf=4"
	fi

	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard" "$channel"

	start_lttng_tracing_ok

	lttng_track_"$domain"_ok "${tracker}"

	prepare_"$domain"_app

	trace_"$domain"_app

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	if [ "$expect_event" -eq 1 ]; then
		trace_matches "$EVENT_NAME" $NR_ITER "$trace_path"
	else
		validate_trace_session_"$domain"_empty "$trace_path"
	fi

	rm -rf "$trace_path"
}

function test_event_vpid_tracker()
{
	local trace_path
	local domain="$1"
	local expect_event="$2"
	local wildcard="$3"
	local channel=''

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"

	if [ "$domain" = kernel ]; then
		channel=chan
		lttng_enable_kernel_channel 1 0 $SESSION_NAME $channel "--subbuf-size=8M --num-subbuf=4"
	fi

	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard" "$channel"

	prepare_"$domain"_app

	start_lttng_tracing_ok

	if [ "$expect_event" -eq 1 ]; then
		lttng_track_"$domain"_ok "--vpid ${CHILD_PID}"
	else
		lttng_track_"$domain"_ok "--vpid $((CHILD_PID+1))"
	fi

	trace_"$domain"_app

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	if [ "$expect_event" -eq 1 ]; then
		validate_trace "$EVENT_NAME" "$trace_path"
	else
		validate_trace_empty "$trace_path"
	fi

	rm -rf "$trace_path"
}

function test_event_pid_tracker()
{
	local trace_path
	local domain="$1"
	local expect_event="$2"
	local wildcard="$3"
	local channel=''

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"

	if [ "$domain" = kernel ]; then
		channel=chan
		lttng_enable_kernel_channel 1 0 $SESSION_NAME $channel "--subbuf-size=8M --num-subbuf=4"
	fi

	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard" "$channel"

	prepare_"$domain"_app

	start_lttng_tracing_ok

	if [ "$expect_event" -eq 1 ]; then
		lttng_track_"$domain"_ok "--pid ${CHILD_PID}"
	else
		lttng_track_"$domain"_ok "--pid $((CHILD_PID+1))"
	fi

	trace_"$domain"_app

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	if [ "$expect_event" -eq 1 ]; then
		validate_trace "$EVENT_NAME" "$trace_path"
	else
		validate_trace_empty "$trace_path"
	fi

	rm -rf "$trace_path"
}


function test_event_tracker_fail()
{
	local trace_path
	local domain="$1"
	local wildcard="$2"
	local tracker="$3"

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"
	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard"
	lttng_track_"$domain"_fail "${tracker}"
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	rm -rf "$trace_path"
}

function test_event_track_untrack()
{
	local trace_path
	local domain="$1"
	local expect_event="$2"
	local wildcard="$3"
	local tracker="$4"
	local channel=''

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"

	if [ "$domain" = kernel ]; then
		channel=chan
		lttng_enable_kernel_channel 1 0 $SESSION_NAME $channel "--subbuf-size=8M --num-subbuf=4"
	fi

	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard" "$channel"

	start_lttng_tracing_ok

	lttng_track_"$domain"_ok "${tracker}"
	lttng_untrack_"$domain"_ok "${tracker}"

	prepare_"$domain"_app

	trace_"$domain"_app

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	if [ "$expect_event" -eq 1 ]; then
		trace_matches "$EVENT_NAME" $NR_ITER "$trace_path"
	else
		validate_trace_session_"$domain"_empty "$trace_path"
	fi

	rm -rf "$trace_path"
}

function test_event_vpid_track_untrack()
{
	local trace_path
	local domain="$1"
	local expect_event="$2"
	local wildcard="$3"
	local channel=''

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"

	if [ "$domain" = kernel ]; then
		channel=chan
		lttng_enable_kernel_channel 1 0 $SESSION_NAME $channel "--subbuf-size=8M --num-subbuf=4"
	fi

	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard" "$channel"

	prepare_"$domain"_app

	start_lttng_tracing_ok

	lttng_track_"$domain"_ok "--vpid ${CHILD_PID}"
	lttng_untrack_"$domain"_ok "--vpid ${CHILD_PID}"

	trace_"$domain"_app

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	if [ "$expect_event" -eq 1 ]; then
		validate_trace "$EVENT_NAME" "$trace_path"
	else
		validate_trace_empty "$trace_path"
	fi

	rm -rf "$trace_path"
}

function test_event_pid_track_untrack()
{
	local trace_path
	local domain="$1"
	local expect_event="$2"
	local wildcard="$3"
	local channel=''

	diag "${FUNCNAME[0]} $*"

	trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path"

	if [ "$domain" = kernel ]; then
		channel=chan
		lttng_enable_kernel_channel 1 0 $SESSION_NAME $channel "--subbuf-size=8M --num-subbuf=4"
	fi

	enable_"$domain"_lttng_event_ok $SESSION_NAME "$wildcard" "$channel"

	prepare_"$domain"_app

	start_lttng_tracing_ok

	lttng_track_"$domain"_ok "--pid ${CHILD_PID}"
	lttng_untrack_"$domain"_ok "--pid ${CHILD_PID}"

	trace_"$domain"_app

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	if [ "$expect_event" -eq 1 ]; then
		validate_trace "$EVENT_NAME" "$trace_path"
	else
		validate_trace_empty "$trace_path"
	fi

	rm -rf "$trace_path"
}

function test_event_ust_vpid_untrack_snapshot()
{
	diag "${FUNCNAME[0]} $*"

	local trace_path=$(mktemp -d -t tmp.${FUNCNAME[0]}_trace_path.XXXXXX)

	create_lttng_session_ok $SESSION_NAME "$trace_path" "--snapshot"

	enable_ust_lttng_event_ok $SESSION_NAME "$EVENT_NAME"

	prepare_ust_app

	lttng_untrack_ust_ok "--vpid --all"

	start_lttng_tracing_ok

	trace_ust_app
	lttng_snapshot_record $SESSION_NAME

	stop_lttng_tracing_ok
	destroy_lttng_session_ok $SESSION_NAME --no-wait

	snapshot_count=$(find "$trace_path" -name metadata | wc -l)
	is "$snapshot_count" 0 "Number of snapshot is zero"

	rm -rf "$trace_path"
}

# MUST set TESTDIR before calling those functions
plan_tests $NUM_TESTS

print_test_banner "$TEST_DESC"

bail_out_if_no_babeltrace

start_lttng_sessiond

diag "Test UST tracker"

if [ ! -x "$TESTAPP_BIN" ]; then
	BAIL_OUT "No UST nevents binary detected."
fi

EVENT_NAME="tp:tptest"

# Both ordering of tracker type and `--all` are valid.
test_event_track_untrack ust 0 "${EVENT_NAME}" "--vgid --all"
test_event_track_untrack ust 0 "${EVENT_NAME}" "--all --vgid"

#vuid, vgid

# non-matching
test_event_tracker ust 0 "${EVENT_NAME}" "--vuid $((SCRIPT_UID+1))"
test_event_tracker ust 0 "${EVENT_NAME}" "--vgid $((SCRIPT_GID+1))"
test_event_tracker ust 0 "${EVENT_NAME}" "--vuid $((SCRIPT_UID+1)) --vgid $((SCRIPT_GID+1))"
test_event_tracker ust 0 "${EVENT_NAME}" "--vuid $((SCRIPT_UID+1)) --vgid ${SCRIPT_GID}"
test_event_tracker ust 0 "${EVENT_NAME}" "--vuid ${SCRIPT_UID} --vgid $((SCRIPT_GID+1))"
test_event_track_untrack ust 0 "${EVENT_NAME}" "--vuid ${SCRIPT_UID}"
test_event_track_untrack ust 0 "${EVENT_NAME}" "--vgid ${SCRIPT_GID}"
test_event_track_untrack ust 0 "${EVENT_NAME}" "--vuid --all"
test_event_track_untrack ust 0 "${EVENT_NAME}" "--vgid --all"

# matching
test_event_tracker ust 1 "${EVENT_NAME}" "--vuid ${SCRIPT_UID}"
test_event_tracker ust 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GID}"
test_event_tracker ust 1 "${EVENT_NAME}" "--vuid ${SCRIPT_UID} --vgid ${SCRIPT_GID}"
test_event_tracker ust 1 "${EVENT_NAME}" "--vuid ${SCRIPT_USERNAME}"
test_event_tracker ust 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GROUPNAME}"
test_event_tracker ust 1 "${EVENT_NAME}" "--vuid ${SCRIPT_UID},$((SCRIPT_UID+1))"
test_event_tracker ust 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GID},$((SCRIPT_GID+1))"
test_event_tracker ust 1 "${EVENT_NAME}" "--vuid ${SCRIPT_USERNAME},$((SCRIPT_UID+1))"
test_event_tracker ust 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GROUPNAME},$((SCRIPT_GID+1))"
test_event_tracker ust 1 "${EVENT_NAME}" "--vuid --all"
test_event_tracker ust 1 "${EVENT_NAME}" "--vgid --all"

#fail
test_event_tracker_fail ust "${EVENT_NAME}" "--vuid lttng_unexisting_user"
test_event_tracker_fail ust "${EVENT_NAME}" "--vgid lttng_unexisting_group"


#vpid

#non-matching
test_event_track_untrack ust 0 "${EVENT_NAME}" "--vpid --all"
test_event_vpid_tracker ust 0 "${EVENT_NAME}"
test_event_vpid_track_untrack ust 0 "${EVENT_NAME}"

#matching
test_event_tracker ust 1 "${EVENT_NAME}" "--vpid --all"
test_event_vpid_tracker ust 1 "${EVENT_NAME}"

#snapshot untrack vpid

test_event_ust_vpid_untrack_snapshot

#pid (backward compat)

#non-matching
test_event_pid_tracker ust 0 "${EVENT_NAME}"
test_event_pid_track_untrack ust 0 "${EVENT_NAME}"
test_event_track_untrack ust 0 "${EVENT_NAME}" "--pid --all"	# backward compat

#matching
test_event_tracker ust 1 "${EVENT_NAME}" "--pid --all"	# backward compat
test_event_pid_tracker ust 1 "${EVENT_NAME}"

check_skip_kernel_test "$NUM_KERNEL_TESTS" "Skipping kernel tracker tests." ||
{
	diag "Test kernel tracker"

	modprobe lttng-test
	ok $? "Loading lttng-test module"

	EVENT_NAME="lttng_test_filter_event"

	# vuid, vgid, vpid

	# non-matching
	test_event_tracker kernel 0 "${EVENT_NAME}" "--vuid $((SCRIPT_UID+1))"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--vgid $((SCRIPT_GID+1))"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--vuid $((SCRIPT_UID+1)) --vgid $((SCRIPT_GID+1))"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--vuid $((SCRIPT_UID+1)) --vgid ${SCRIPT_GID}"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--vuid ${SCRIPT_UID} --vgid $((SCRIPT_GID+1))"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--vuid ${SCRIPT_UID}"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--vgid ${SCRIPT_GID}"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--vuid --all"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--vgid --all"

	# matching
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vuid ${SCRIPT_UID}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GID}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vuid ${SCRIPT_UID} --vgid ${SCRIPT_GID}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vuid ${SCRIPT_USERNAME}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GROUPNAME}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vuid ${SCRIPT_UID},$((SCRIPT_UID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GID},$((SCRIPT_GID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vuid ${SCRIPT_USERNAME},$((SCRIPT_UID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vgid ${SCRIPT_GROUPNAME},$((SCRIPT_GID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vuid --all"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vgid --all"

	#fail
	test_event_tracker_fail kernel "${EVENT_NAME}" "--vuid lttng_unexisting_user"
	test_event_tracker_fail kernel "${EVENT_NAME}" "--vgid lttng_unexisting_group"


	#uid, gid

	# non-matching
	test_event_tracker kernel 0 "${EVENT_NAME}" "--uid $((SCRIPT_UID+1))"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--gid $((SCRIPT_GID+1))"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--uid $((SCRIPT_UID+1)) --gid $((SCRIPT_GID+1))"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--uid $((SCRIPT_UID+1)) --gid ${SCRIPT_GID}"
	test_event_tracker kernel 0 "${EVENT_NAME}" "--uid ${SCRIPT_UID} --gid $((SCRIPT_GID+1))"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--uid ${SCRIPT_UID}"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--gid ${SCRIPT_GID}"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--uid --all"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--gid --all"

	# matching
	test_event_tracker kernel 1 "${EVENT_NAME}" "--uid ${SCRIPT_UID}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--gid ${SCRIPT_GID}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--uid ${SCRIPT_UID} --gid ${SCRIPT_GID}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--uid ${SCRIPT_USERNAME}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--gid ${SCRIPT_GROUPNAME}"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--uid ${SCRIPT_UID},$((SCRIPT_UID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--gid ${SCRIPT_GID},$((SCRIPT_GID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--uid ${SCRIPT_USERNAME},$((SCRIPT_UID+1))"
	test_event_tracker kernel 1 "${EVENT_NAME}" "--gid ${SCRIPT_GROUPNAME},$((SCRIPT_GID+1))"

	#fail
	test_event_tracker_fail kernel "${EVENT_NAME}" "--uid lttng_unexisting_user"
	test_event_tracker_fail kernel "${EVENT_NAME}" "--gid lttng_unexisting_group"


	#vpid

	#non-matching
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--vpid --all"
	test_event_vpid_tracker kernel 0 "${EVENT_NAME}"
	test_event_vpid_track_untrack kernel 0 "${EVENT_NAME}"

	#matching
	test_event_tracker kernel 1 "${EVENT_NAME}" "--vpid --all"
	test_event_vpid_tracker kernel 1 "${EVENT_NAME}"

	#pid

	#non-matching
	test_event_pid_tracker kernel 0 "${EVENT_NAME}"
	test_event_pid_track_untrack kernel 0 "${EVENT_NAME}"
	test_event_track_untrack kernel 0 "${EVENT_NAME}" "--pid --all"

	#matching
	test_event_tracker kernel 1 "${EVENT_NAME}" "--pid --all"
	test_event_pid_tracker kernel 1 "${EVENT_NAME}"

	# When using session destroy with '--no-wait', the quick
	# exit at the end means that the lttng-test modules is
	# still marked as being in use and 'modprobe --remove' fails.
	#
	# It is possible to work around the issue by not setting
	# '--no-wait' on the last kernel test, but it seems like
	# an easy detail to forget about.
	#
	# The sleep here gives the system a small amount of time to
	# finish wrapping up the session that had the lttng-test kernel
	# module loaded.
	sleep 1

	modprobe --remove lttng-test
	ok $? "Unloading lttng-test module"
}

stop_lttng_sessiond
