#!/bin/bash
#
# Copyright (C) - 2013 Christian Babeux <christian.babeux@efficios.com>
# Copyright (C) - 2014 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License, version 2 only, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

TEST_DESC="Kernel tracer - System calls"

CURDIR=$(dirname $0)/
TESTDIR=$CURDIR/../..
NUM_TESTS=157

# test command issues at least open and close system calls
TESTCMD="cat /proc/cpuinfo > /dev/null"

source $TESTDIR/utils/utils.sh

function validate_list()
{
	local session_name=$1
	local event_name=$2
	local opt=$3

	out=$($TESTDIR/../src/bin/lttng/$LTTNG_BIN list $session_name $opt | grep $event_name)
	if [ -z "$out" ]; then
		fail "Validate syscall listing"
		diag "$event_name not found when listing $session_name (opt $opt)"
	else
		pass "Validate syscall listing. Found $event_name."
	fi
}

function test_syscall_event_list()
{
	local EVENT_NAME="getpid"

	diag "Syscall event listing"

	validate_list "" $EVENT_NAME "-k --syscall"
}

function test_syscall_simple_list()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_simple_list"
	local EVENT_NAME="close"

	diag "Syscall simple listing"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME $EVENT_NAME

	validate_list $SESSION_NAME $EVENT_NAME

	start_lttng_tracing_ok
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure each is there.
	validate_trace_exp "-e syscall_entry_$EVENT_NAME: -e compat_syscall_entry_$EVENT_NAME:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_$EVENT_NAME: -e compat_syscall_exit_$EVENT_NAME:" $TRACE_PATH

	# ensure trace only contains those.
	validate_trace_only_exp "-e syscall_entry_$EVENT_NAME: -e compat_syscall_entry_$EVENT_NAME: -e syscall_exit_$EVENT_NAME: -e compat_syscall_exit_$EVENT_NAME:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_simple_list_two()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_simple_list_two"
	local EVENT_NAME="close"
	local EVENT_NAME2="read"

	diag "Syscall simple listing"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME $EVENT_NAME
	lttng_enable_kernel_syscall_ok $SESSION_NAME $EVENT_NAME2

	validate_list $SESSION_NAME $EVENT_NAME
	validate_list $SESSION_NAME $EVENT_NAME2

	start_lttng_tracing_ok
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure each is there.
	validate_trace_exp "-e syscall_entry_$EVENT_NAME: -e compat_syscall_entry_$EVENT_NAME:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_$EVENT_NAME: -e compat_syscall_exit_$EVENT_NAME:" $TRACE_PATH

	# ensure each is there.
	validate_trace_exp "-e syscall_entry_$EVENT_NAME2: -e compat_syscall_entry_$EVENT_NAME2:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_$EVENT_NAME2: -e compat_syscall_exit_$EVENT_NAME2:" $TRACE_PATH

	# ensure trace only contains those.
	validate_trace_only_exp "-e syscall_entry_$EVENT_NAME: -e compat_syscall_entry_$EVENT_NAME: -e syscall_exit_$EVENT_NAME: -e compat_syscall_exit_$EVENT_NAME: -e syscall_entry_$EVENT_NAME2: -e compat_syscall_entry_$EVENT_NAME2: -e syscall_exit_$EVENT_NAME2: -e compat_syscall_exit_$EVENT_NAME2:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_single()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_single"

	diag "Syscall trace single"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"

	start_lttng_tracing_ok
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure each is there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH

	# ensure trace only contains those.
	validate_trace_only_exp "-e syscall_entry_open: -e compat_syscall_entry_open: -e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_two()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_two"

	diag "Syscall trace two events"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"
	lttng_enable_kernel_syscall_ok $SESSION_NAME "close"

	start_lttng_tracing_ok
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure each is there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_entry_close: -e compat_syscall_entry_close:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_close: -e compat_syscall_exit_close:" $TRACE_PATH

	# ensure trace only contains those.
	validate_trace_only_exp "-e syscall_entry_open: -e compat_syscall_entry_open: -e syscall_exit_open: -e compat_syscall_exit_open: -e syscall_entry_close: -e compat_syscall_entry_close: -e syscall_exit_close: -e compat_syscall_exit_close:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_all()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_all"

	diag "Syscall trace all events"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable all syscalls
	lttng_enable_kernel_syscall_ok $SESSION_NAME

	start_lttng_tracing_ok
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure at least open and close are there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_entry_close: -e compat_syscall_entry_close:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_close: -e compat_syscall_exit_close:" $TRACE_PATH
	# trace may contain other syscalls.

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_all_disable_one()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_all_disable_one"

	diag "Syscall trace all events and disable one"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable all syscalls
	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# try to disable open system call: fails because enabler semantic of
	# "all syscalls" is not "the open" system call.
	lttng_disable_kernel_syscall_fail $SESSION_NAME "open"

	start_lttng_tracing_ok
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure "open" syscall is there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open: -e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH

	# ensure "close" syscall is there.
	validate_trace_exp "-e syscall_entry_close: -e compat_syscall_entry_close:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_close: -e compat_syscall_exit_close:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_all_disable_two()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_all_disable_two"

	diag "Syscall trace all events and disable two"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable all syscalls
	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# try to disable open and close system calls: fails because enabler
	# semantic of "all syscalls" is not "the open" system call.
	lttng_disable_kernel_syscall_fail $SESSION_NAME "open"
	lttng_disable_kernel_syscall_fail $SESSION_NAME "close"

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure "open" syscall is there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open: -e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH

	# ensure "close" syscall is there.
	validate_trace_exp "-e syscall_entry_close: -e compat_syscall_entry_close:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_close: -e compat_syscall_exit_close:" $TRACE_PATH

	# ensure "execve" syscall is there.
	validate_trace_exp "-e syscall_entry_execve: -e compat_syscall_entry_execve:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_execve: -e compat_syscall_exit_execve:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_all_disable_all()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_all_disable_all"

	diag "Syscall trace all events and disable all"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable all system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# disable all system calls
	lttng_disable_kernel_syscall_ok $SESSION_NAME

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure nothing has been traced.
	validate_trace_empty $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_all_disable_all_enable_all()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_all_disable_all_enable_all"

	diag "Syscall trace all events and enable/disable all"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable all system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# disable all system calls
	lttng_disable_kernel_syscall_ok $SESSION_NAME
	# enable all system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure at least open and close are there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_entry_close: -e compat_syscall_entry_close:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_close: -e compat_syscall_exit_close:" $TRACE_PATH
	# trace may contain other syscalls.

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_all_disable_all_twice()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_all_disable_all_twice"

	diag "Syscall trace all events and enable/disable all twice"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable all system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# disable all system calls
	lttng_disable_kernel_syscall_ok $SESSION_NAME
	# enable all system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# disable all system calls
	lttng_disable_kernel_syscall_ok $SESSION_NAME

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure nothing has been traced.
	validate_trace_empty $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_one_disable_one()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_one_disable_one"

	diag "Syscall trace one event and disable one"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable open system call
	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"
	# disable open system call
	lttng_disable_kernel_syscall_ok $SESSION_NAME "open"

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure nothing has been traced.
	validate_trace_empty $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_two_disable_two()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_two_disable_two"

	diag "Syscall trace two events and disable two"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable open and close system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"
	lttng_enable_kernel_syscall_ok $SESSION_NAME "close"
	# disable open and close system calls
	lttng_disable_kernel_syscall_ok $SESSION_NAME "open"
	lttng_disable_kernel_syscall_ok $SESSION_NAME "close"

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure nothing has been traced.
	validate_trace_empty $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_two_disable_one()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_two_disable_one"

	diag "Syscall trace two events and disable one"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# enable open and close system calls
	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"
	lttng_enable_kernel_syscall_ok $SESSION_NAME "close"
	# disable close system call
	lttng_disable_kernel_syscall_ok $SESSION_NAME "close"

	start_lttng_tracing_ok
	# generates open, close, execve (at least)
	eval ${TESTCMD}
	stop_lttng_tracing_ok

	# ensure open is there.
	validate_trace_exp "-e syscall_entry_open: -e compat_syscall_entry_open:" $TRACE_PATH
	validate_trace_exp "-e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH

	# ensure trace only contains those.
	validate_trace_only_exp "-e syscall_entry_open: -e compat_syscall_entry_open: -e syscall_exit_open: -e compat_syscall_exit_open:" $TRACE_PATH

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_disable_twice()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_disable_twice"

	diag "Syscall trace one event and disable twice"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"
	# First disable will succeed
	lttng_disable_kernel_syscall_ok $SESSION_NAME "open"
	# Second disable succeeds too, due to enabler semantic.
	lttng_disable_kernel_syscall_ok $SESSION_NAME "open"

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_disable_all_twice()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_disable_all_twice"

	diag "Syscall trace all events and disable all twice"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# First disable will succeed
	lttng_disable_kernel_syscall_ok $SESSION_NAME
	# Second disable succeeds too, due to enabler semantic.
	lttng_disable_kernel_syscall_ok $SESSION_NAME

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}


function test_syscall_enable_unknown()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_unknown"

	diag "Syscall enable an unknown event"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	# Enabling a syscall that does not exist succeeds, due to enabler
	# semantic.
	lttng_enable_kernel_syscall_ok $SESSION_NAME "thissyscallcannotexist"

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_all_enable_one()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_all_enable_one"

	diag "Syscall enable all and enable one"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME
	# Enabling an event already enabled succeeds, due to enabler semantic.
	lttng_enable_kernel_syscall_ok $SESSION_NAME "open"

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_disable_all_disable_one()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_all_enable_one"

	diag "Syscall disable all and disable one"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_syscall_ok $SESSION_NAME
	lttng_disable_kernel_syscall_ok $SESSION_NAME
	# Disabling an event already disabled fails.
	lttng_disable_kernel_syscall_fail $SESSION_NAME "open"

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_channel_disable_all()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_channel_disable_all"
	CHANNEL_NAME="channel"

	diag "Syscall enable channel and disable all"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_channel_ok $SESSION_NAME $CHANNEL_NAME
	# sessiond semantic for "disable all" is to try to match all enalers
	# it knowns about. Disable all succeeds if it finds no match.
	lttng_disable_kernel_syscall_ok $SESSION_NAME "-a" $CHANNEL_NAME

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

function test_syscall_enable_channel_disable_one()
{
	TRACE_PATH=$(mktemp -d)
	SESSION_NAME="kernel_syscall_enable_channel_disable_all"
	CHANNEL_NAME="channel"

	diag "Syscall enable channel and disable one"

	create_lttng_session_ok $SESSION_NAME $TRACE_PATH

	lttng_enable_kernel_channel_ok $SESSION_NAME $CHANNEL_NAME
	lttng_disable_kernel_syscall_fail $SESSION_NAME "open" $CHANNEL_NAME

	destroy_lttng_session_ok $SESSION_NAME

	rm -rf $TRACE_PATH
}

# MUST set TESTDIR before calling those functions
plan_tests $NUM_TESTS

print_test_banner "$TEST_DESC"

if [ "$(id -u)" == "0" ]; then
	isroot=1
else
	isroot=0
fi

skip $isroot "Root access is needed. Skipping all tests." $NUM_TESTS ||
{
	start_lttng_sessiond

	test_syscall_event_list
	test_syscall_simple_list
	test_syscall_simple_list_two
	test_syscall_single
	test_syscall_two
	test_syscall_all
	test_syscall_all_disable_one
	test_syscall_all_disable_two
	test_syscall_enable_all_disable_all
	test_syscall_enable_all_disable_all_enable_all
	test_syscall_enable_all_disable_all_twice
	test_syscall_enable_one_disable_one
	test_syscall_enable_two_disable_two
	test_syscall_enable_two_disable_one
	test_syscall_disable_twice
	test_syscall_disable_all_twice
	test_syscall_enable_unknown
	test_syscall_enable_all_enable_one
	test_syscall_disable_all_disable_one
	test_syscall_enable_channel_disable_all
	test_syscall_enable_channel_disable_one

	stop_lttng_sessiond
}
