# This test has two purposes:
# 1. To verify that uprobes is correctly filtering unwanted processes,
#    allowing stap scripts to run at full speed
# 2. To verify that stapiu_probe_prehandler() is correctly filtering
#    unwanted processes
# This is done by comparing the number of calls to
# stapiu_probe_prehandler() and stapiu_probe_handler() using the
# -DUPROBES_HITCOUNT define.added in commit 68561b3.

# Note that this test relies on the basic assumption that there are no
# other stap sessions or uprobes inserted in the benchmark inode of
# stap. Otherwise, all hitcounts will be thrown off.

set test "uprobes_filtering"

# We need to actually run things
if {! [installtest_p]} { untested $test; return }

# If we're not using inode uprobes, don't bother testing.
if {! [inode_uprobes_p]} { untested $test; return }

# So that sudo stap works in case we're not running the test as root
set installed_stap "$env(SYSTEMTAP_PATH)/stap"

# Testing steps:
# 1. check if we're root or user using id -u
# 2. run mark("benchmark") w/ --privilege=stapusr
# 3. run benchmark as user
# 4. run benchmark as root
# 5. stop process in 2.
# 6. Check that the handler was called only for the right process
# 7. Check that the wrong process was filtered out
#    7a. First check if kernel prefiltering is supported
#    7b. Verify hitcount results depending on prefiltering support

# 1. Check if we're root or user
set am_root [expr 0 == [exec id -u]]

# 2. Start the background benchmark hitcounter
spawn stap -DUPROBES_HITCOUNT --privilege=stapusr --vp 00003 \
	-e "probe process(\"stap\").mark(\"benchmark\") \{ next \}"
set stap_id $spawn_id
set failed 1
expect {
	-timeout 30
	-re {systemtap_module_init\(\)\ returned\ 0} { set failed 0 }
	timeout {exec kill -s KILL -- -[exp_pid -i $stap_id]}
}
if {$failed} {
	fail "$test (timeout: can't start benchmark monitor)"
	catch { close -i $stap_id } ; catch { wait -i $stap_id }
	return
} else {
	pass "$test (started benchmark monitor)"
}

# 3. Run benchmark as user
set non_root_loops 11111111
if {[as_non_root "$installed_stap \
		--benchmark-sdt-loops=$non_root_loops"]} {
	fail "$test (can't start non-root benchmark)"
} else {
	pass "$test (started non-root benchmark)"
}

# 4. Run benchmark as root
set root_loops 10101010
if {[as_root "$installed_stap --benchmark-sdt-loops=$root_loops"]} {
	fail "$test (can't start root benchmark)"
} else {
	pass "$test (started root benchmark)"
}

# 5. Stop process from step 2
exec kill -s TERM [exp_pid -i $stap_id]
set prehandler_hitcount -1
set handler_hitcount -1
expect {
	-timeout 30
	-i $stap_id
	-re {stapiu_probe_prehandler\(\)\ called\ ([0-9]+)\ times} {
		set prehandler_hitcount $expect_out(1,string)
		exp_continue
	}
	-re {stapiu_probe_handler\(\)\ called\ ([0-9]+)\ times} {
		set handler_hitcount $expect_out(1,string)
	}
	timeout {
		fail "$test (timeout: can't stop benchmark monitor)"
		exec kill -s KILL -- -[exp_pid -i $stap_id]
	}
}
catch { close -i $stap_id } ; catch { wait -i $stap_id }

if {$prehandler_hitcount == -1 || $handler_hitcount == -1} {
	fail "$test (could not get prehandler/handler hitcount)"
	verbose -log "this happens if stap doesn't support UPROBES_HITCOUNT"
	return
}

# 6. Check that the handler was called only for the right process
#    This means that if we're root, then handler() should have been
#    called $root_loops times only, and vice-versa for not root
if {(!$am_root && $handler_hitcount != $non_root_loops)
		|| ($am_root && $handler_hitcount != $root_loops)} {
	fail "$test (handler was not called the right number of times)"
	if {$am_root} {set n $root_loops} else {set n $non_root_loops}
	verbose -log "expected number of calls: $n"
	verbose -log "actual number of calls: $handler_hitcount"
	return
}

# 7. Check that the wrong process was filtered out

# 7a. First check if kernel prefiltering is supported

# Relies on the fact that stap won't pass compilation if not supported
set prefilter_support [expr ![catch {exec stap -p4 -g \
		-e "probe begin \{%\{UPROBE_HANDLER_REMOVE%\}\}" 2> /dev/null}]]
verbose -log "kernel prefiltering support = $prefilter_support"

# 7b. Verify hitcount results depending on prefiltering support

# If prefiltering is not supported, then we expect the prehandler to
# be called $root_loops + $non_root_loops times
if {!$prefilter_support} {
	set total [expr {$root_loops + $non_root_loops}]
	if {$prehandler_hitcount != $total} {
		fail "$test (prehandler was not called the right number of times)"
		verbose -log "expected number of calls: $total"
		verbose -log "actual number of calls: $prehandler_hitcount"
		return
	}
} else {
# Prefiltering is supported, so we expect the prehandler to be called
# only one time more than handler.

# TODO: Verify that it will always be only 1 greater. From a look at
# kernel/events/uprobes.c, it seems that in the handler_chain() func,
# after handler() returns, the next call is unapply_uprobe() (assuming
# we are the only consumers for this probe), which will remove the
# uprobe. Since the handler() call is wrapped in semaphores, we should
# get no other calls before the first handler_chain() finishes.
	set total [expr {$handler_hitcount + 1}]
	if {$prehandler_hitcount != $total} {
		fail "$test (prehandler was not called the right number of times)"
		verbose -log "expected number of calls: $total"
		verbose -log "actual number of calls: $prehandler_hitcount"
		verbose -log "this can happen if other uprobes are inserted at\
			the same location"
		return
	}
}

pass $test

