set test "unprivileged myproc"

if {![installtest_p]} { untested $test; return }
if {![uprobes_p]} { untested $test; return }

set exepath "loop"
set sopath "libloop.so"

# These probe types should fire unprivileged users, but only within their own processes.
set myproc_probe_types [list \
  "process.begin" \
  "process.end" \
  "process.function(string)" \
  "process.function(number)" \
  "process.function(number).call" \
  "process.function(number).inline" \
  "process.function(number).return" \
  "process.function(number).exported" \
  "process.function(string).call" \
  "process.function(string).inline" \
  "process.function(string).label(string)" \
  "process.function(string).return" \
  "process.function(string).exported" \
  "process.plt" \
  "process.plt.statement(number)" \
  "process.plt(string)" \
  "process.plt(string).statement(number)" \
  "process.library(string).function(number)" \
  "process.library(string).function(number).call" \
  "process.library(string).function(number).inline" \
  "process.library(string).function(number).return" \
  "process.library(string).function(number).exported" \
  "process.library(string).function(string)" \
  "process.library(string).function(string).call" \
  "process.library(string).function(string).inline" \
  "process.library(string).function(string).return" \
  "process.library(string).function(string).exported" \
  "process.library(string).function(string).label(string)" \
  "process.library(string).plt" \
  "process.library(string).plt.statement(number)" \
  "process.library(string).plt(string)" \
  "process.library(string).plt(string).statement(number)" \
  "process.library(string).mark(string)" \
  "process.library(string).provider(string).mark(string)" \
  "process.library(string).statement(number)" \
  "process.library(string).statement(string)" \
  "process.provider(string).mark(string)" \
  "process.mark(string)" \
  "process.statement(number)" \
  "process.statement(string)" \
  "process.syscall" \
  "process.syscall.return" \
  "process.thread.begin" \
  "process.thread.end" \
  "process(number).begin" \
  "process(number).end" \
  "process(number).insn" \
  "process(number).insn.block" \
  "process(number).statement(number).absolute" \
  "process(number).statement(number).absolute.return" \
  "process(number).syscall" \
  "process(number).syscall.return" \
  "process(number).thread.begin" \
  "process(number).thread.end" \
  "process(string).begin" \
  "process(string).end" \
  "process(string).function(number)" \
  "process(string).function(number).call" \
  "process(string).function(number).inline" \
  "process(string).function(number).return" \
  "process(string).function(number).exported" \
  "process(string).function(string)" \
  "process(string).function(string).call" \
  "process(string).function(string).inline" \
  "process(string).function(string).return" \
  "process(string).function(string).exported" \
  "process(string).insn" \
  "process(string).insn.block" \
  "process(string).library(string).function(number)" \
  "process(string).library(string).function(number).call" \
  "process(string).library(string).function(number).inline" \
  "process(string).library(string).function(number).return" \
  "process(string).library(string).function(number).exported" \
  "process(string).library(string).function(string)" \
  "process(string).library(string).function(string).call" \
  "process(string).library(string).function(string).inline" \
  "process(string).library(string).function(string).return" \
  "process(string).library(string).function(string).exported" \
  "process(string).library(string).function(string).label(string)" \
  "process(string).library(string).mark(string)" \
  "process(string).library(string).provider(string).mark(string)" \
  "process(string).library(string).statement(number)" \
  "process(string).library(string).statement(string)" \
  "process(string).library(string).plt" \
  "process(string).library(string).plt.statement(number)" \
  "process(string).library(string).plt(string)" \
  "process(string).library(string).plt(string).statement(number)" \
  "process(string).mark(string)" \
  "process(string).provider(string).mark(string)" \
  "process(string).statement(number)" \
  "process(string).statement(string)" \
  "process(string).plt" \
  "process(string).plt.statement(number)" \
  "process(string).plt(string)" \
  "process(string).plt(string).statement(number)" \
  "process(string).syscall" \
  "process(string).syscall.return" \
  "process(string).thread.begin" \
  "process(string).thread.end" \
  "process(string).function(string).label(string)" \
]

# Add arguments to a probe type template.
proc add_args { probe_type pid } {
    global exepath sopath
    global addr_of_bar addr_of_libloopfunc

    set probe "$probe_type"

    # Replace "hz(number)" with "hz(1000)"
    regsub -all "hz\\(number\\)" $probe "hz(1000)" probe
    # Replace "ns(number)" with "ns(100000)"
    regsub -all "ns\\(number\\)" $probe "ns(100000)" probe
    # Replace "nsec(number)" with "nsec(100000)"
    regsub -all "nsec\\(number\\)" $probe "nsec(100000)" probe
    # Replace "us(number)" with "us(100)"
    regsub -all "us\\(number\\)" $probe "us(100)" probe
    # Replace "usec(number)" with "usec(100)"
    regsub -all "usec\\(number\\)" $probe "usec(100)" probe
    # Replace "process(number)" with "process($pid)"
    regsub -all "process\\(number\\)" $probe "process($pid)" probe

    # Replace "library(string).function(string).inline" with "library(string).function("ilibloopfunc").inline"
    regsub -all "library\\(string\\).function\\(string\\).inline" $probe "library(string).function(\"ilibloopfunc\").inline" probe

    # Replace "function(string).inline" with "function("ibar").inline"
    regsub -all "function\\(string\\).inline" $probe "function(\"ibar\").inline" probe

    # Replace "library(string).function(string)" with "library(string.function("libloopfunc")"
    regsub -all "library\\(string\\).function\\(string\\)" $probe "library(string).function(\"libloopfunc\")" probe
    # Replace "library(string).function(number)" with "library(string).function($addr_of_libloopfunc)"
    regsub -all "library\\(string\\).function\\(number\\)" $probe "library(string).function($addr_of_libloopfunc)" probe

    # Replace "function(string)" with "function("bar")"
    regsub -all "function\\(string\\)" $probe "function(\"bar\")" probe
    # Replace "function(number)" with "function($addr_of_bar)"
    regsub -all "function\\(number\\)" $probe "function($addr_of_bar)" probe

    # Replace "label(string)" with "label("a")"
    regsub -all "label\\(string\\)" $probe "label(\"a\")" probe

    # Replace "library(string).mark(string)" with "library(string).mark("libloopfunc_enter")"
    regsub -all "library\\(string\\).mark\\(string\\)" $probe "library(string).mark(\"libloopfunc_enter\")" probe
    # Replace "library(string).provider(string).mark(string)" with "library(string).provider(string).mark("libloopfunc_enter")"
    regsub -all "library\\(string\\).provider\\(string\\).mark\\(string\\)" $probe "library(string).provider(string).mark(\"libloopfunc_enter\")" probe
    # Replace "mark(string)" with "mark("main_enter")"
    regsub -all "mark\\(string\\)" $probe "mark(\"main_enter\")" probe

    # Replace "library(string).statement(number)" with "library(string).statement($addr_of_libloopfunc)"
    regsub -all "library\\(string\\).statement\\(number\\)" $probe "library(string).statement($addr_of_libloopfunc)" probe
    # Replace "library(string).statement(string)" with "library(string).statement("libloopfunc@libloop.c:*")"
    regsub -all "library\\(string\\).statement\\(string\\)" $probe "library(string).statement(\"libloopfunc@libloop.c:*\")" probe

    # Replace "library(string).plt.statement(number)" with
    # "library(string).plt.statement($addr_of_libloopfunc)"
    regsub -all "library\\(string\\).plt.statement\\(number\\)" $probe "library(string).plt.statement($addr_of_libloopfunc)" probe
    # Replace "library(string).plt(string).statement(number)" with
    # "library(string).plt(string).statement($addr_of_libloopfunc)"
    regsub -all "library\\(string\\).plt\\(string\\).statement\\(number\\)" $probe "library(string).plt(string).statement($addr_of_libloopfunc)" probe

    # Replace "statement(number)" with "statement($addr_of_bar)"
    regsub -all "statement\\(number\\)" $probe "statement($addr_of_bar)" probe
    # Replace "statement(string)" with "statement("bar@loop.c:*")"
    regsub -all "statement\\(string\\)" $probe "statement(\"bar@loop.c:*\")" probe

    # Replace "library(string)" with "library("$sopath")"
    regsub -all "library\\(string\\)" $probe "library(\"$sopath\")" probe

    # Replace "provider(string)" with "provider("_test_")"
    regsub -all "provider\\(string\\)" $probe "provider(\"_test_\")" probe

    # Replace plt with plt("*") (all shlib calls)
    regsub -all "plt\\(string\\)" $probe "plt(\"*\")" probe

    # Replace "(number)" with "(10)"
    regsub -all "\\(number\\)" $probe "(10)" probe
    # Replace "(string)" with "(\"$exepath\")"
    regsub -all "\\(string\\)" $probe "(\"./$exepath\")" probe

    return "$probe"
}

# Add required extra options according to the probe type.
proc extra_options { probe_type } {
    global exepath

    # Add '-c $exepath 1' for process.* probes.
    if {[regexp "^process\\." $probe_type]} {
	return "-c \"./$exepath 1\""
    }

    return ""
}

# Test valid probe types
proc test_probes { privilege } {
    global myproc_probe_types allowed test srcdir subdir tested
    
    set effective_uid [exec /usr/bin/id -u]
    set stap [exec /usr/bin/which stap]
    
    # Start the test executable in the background
    set our_pid [exec ./loop &]
    verbose -log "our ./loop pid is $our_pid"

    # If we are not root start another one as root. It will be a failure if any probes fail against
    # this instance.
    if {$effective_uid != 0} {
	# ask for password here, if required ...
	as_root { true }
	# ... and not here.
	exec sudo ./loop &
	# Give it a chance to start
	sleep 1
	set root_pid [exec ps -fe | awk {/^root.*[^o] \.\/loop/ { print $2 }}]
	verbose -log "root's ./loop pid is '$root_pid'"
    }

    # Test using --unprivileged and --privilege=stapusr. They should be equivalent
    set stapusr_options [list --unprivileged --privilege=stapusr ]
    # Test using no option and --privilege=stapdev. They should be equivalent
    set stapdev_options [list "" --privilege=stapdev ]

    set selector 0
    foreach probe_type $myproc_probe_types {
	# Select the proper --privilege option
	switch $privilege {
	    stapusr {
		set option [lindex $stapusr_options $selector]
	    }
	    stapdev {
		set option [lindex $stapdev_options $selector]
	    }
	    default {
		set option "--privilege=$privilege"
	    }
	}

	# There are currently some untestable probe types
	if {[regexp "\.plt" $probe_type] && ! [plt_probes_p]} {
	    #  .plt is only supported on certain arches
	    untested "$test: $option $probe_type"
	    set tested($probe_type) 1
	    continue
	}

	switch $probe_type {
	    process(number).thread.begin -
	    process(number).thread.end
	    {
		# Impossible to predict the task_id of a thread
		untested "$test: $option $probe_type"
		set tested($probe_type) 1
		continue
	    }
	    process(number).insn -
	    process(number).insn.block -
	    process(string).insn -
	    process(string).insn.block
	    {
		# These probe types are not valid for unprivileged users.
		if {$privilege == "stapusr"} {
		    untested "$test: $option $probe_type"
		    set tested($probe_type) 1
		    continue
		}
	    }
	}

	# There are currently some known and expected failures
	switch $probe_type {
	    process.library(string).function(number).inline -
	    process(string).library(string).function(number).inline -
	    process.function(number).inline -
	    process(string).function(number).inline
	    {
		setup_kfail GCC *-*-*
	    }
	    process.function(number).return -
	    process.function(string).return -
	    process.library(string).function(number).return -
	    process.library(string).function(string).return -
	    process(string).function(number).return -
	    process(string).function(string).return -
	    process(string).library(string).function(number).return -
	    process(string).library(string).function(string).return
	    {
		if {![uretprobes_p]} { setup_kfail URETPROBES *-*-* }
	    }
	    process(number).statement(number).absolute
	    {
		# Inode uprobes don't support 'absolute' probes.
		if {[inode_uprobes_p]} { setup_kfail INODE_UPROBES *-*-* }
	    }
	    process(number).statement(number).absolute.return
	    {
		# 'absolute.return' probes could fail because we're
		# using inode uprobes or we don't have uretprobes.
		if {[inode_uprobes_p]} {
		    setup_kfail INODE_UPROBES *-*-*
		} elseif {![uretprobes_p]} {
		    setup_kfail URETPROBES *-*-*
		}
	    }
	    process(number).insn -
	    process(number).insn.block -
	    process(string).insn -
	    process(string).insn.block
	    {
		if {![utrace_orig_p]} { setup_kfail ITRACE *-*-* }
	    }
	}

	# Test the probe type. If successful, the stap rc will be 0.
	set probe [add_args $probe_type $our_pid]
	set cmd [concat [list $stap $option -e "probe $probe { if (is_myproc ()) println (\"is myproc\") else println (\"not myproc\"); exit(); } probe timer.s(15) { println(\"timeout\") exit() }"] [extra_options $probe_type]]
	    
	# If the probe types was 'process(number).end' or 'process(string).end' then set up a
	# delayed kill of our test program in order to make the probe fire.
	if {[regexp "^process\\(number\\)\\.end" $probe_type] || \
		[regexp "^process\\(string\\)\\.end" $probe_type]} {
	    exec $srcdir/$subdir/delayedkill $our_pid 10 &
	}

	# Run stap.
	verbose -log "eval exec $cmd"
	set rc [catch {eval exec $cmd} res_stap]
	verbose -log "rc == $rc"
	verbose -log "$res_stap"

	if {$privilege == "stapusr"} {
	    # The test passes if it returns 0 and prints "is myproc".
	    if {$rc == 0 && [regexp ".*is myproc.*" "$res_stap"]} {
		pass "$test: $option $probe_type"
	    } else {
		fail "$test: $option $probe_type"
	    }
	} else {
	    # The test passes if it returns 0
	    if {$rc == 0} {
		pass "$test: $option $probe_type"
	    } else {
		fail "$test: $option $probe_type"
	    }
	}
	
	# Indicate that this probe type has been tested
	set tested($probe_type) 1

	# If the probe types was 'process(number).end' or 'process(string).end', then our test
	# program was killed in order to make the probe fire. Restart it.
	if {[regexp "^process\\(number\\)\\.end" $probe_type] || \
		[regexp "^process\\(string\\)\\.end" $probe_type]} {
	    set our_pid [exec ./loop &]
	    verbose -log "our ./loop pid is $our_pid"
	}

	set selector [expr ($selector + 1) % 2]

	# This test suite runs for a long time. There will be an as_root command at the end. Keep
	# sudo status alive in case it requires a password in order to avoid being prompted again
	# for it.
	as_root { true }
    }

    # Kill the the test executable(s)
    catch { exec kill $our_pid }
    if {$effective_uid != 0} {
	as_root "kill $root_pid"
    }
}

# Need to build a user shared library.
set libflags [sdt_includes]
set libflags "$libflags additional_flags=-g"
set libflags "$libflags additional_flags=-O"
set libflags "$libflags additional_flags=-Wall"
set libflags "$libflags additional_flags=-Werror"
set libflags "$libflags additional_flags=-I."
set libflags "$libflags additional_flags=-shared"
set libflags "$libflags additional_flags=-fPIC"
set res [target_compile $srcdir/$subdir/libloop.c $sopath executable $libflags ]
if { $res == "" } {
    pass "$test library compile"
} else {
    fail "$test library compile: $res"
    untested "$test Tests"
    return
}

# Need to build a user application
set exeflags "additional_flags=-g"
set exeflags "$exeflags additional_flags=-O"
set exeflags "$exeflags additional_flags=-lpthread"
set exeflags "$exeflags [sdt_includes]"
set exeflags "$exeflags additional_flags=-Wl,-rpath,[pwd]"
set exeflags "$exeflags additional_flags=-L[pwd] additional_flags=-lloop"
set exeflags "$exeflags compiler=gcc"
# ppc64 needs a more restrictive constraint for the probe args
if {[regexp "^(x86_64|i.86)$" $::tcl_platform(machine)] == 0} {
set exeflags "$exeflags additional_flags=-DSTAP_SDT_ARG_CONSTRAINT=nr"
}
set res [target_compile $srcdir/systemtap.unprivileged/loop.c $exepath executable "$exeflags"]
if { $res == "" } {
    pass "$test exe compile"
} else {
    fail "$test exe compile: $res"
    untested "$test Tests"
    return
}

# Extract needed addresses. Note, some architectures add a '.' to the
# function name, so match that optionally.
set addr_of_bar [exec objdump -d $exepath | awk {/<\.?bar>:/ { printf "0x%s\n",$1 }}]
verbose -log "addr_of_bar: $addr_of_bar"
set addr_of_libloopfunc [exec objdump -d $sopath | awk {/<\.?libloopfunc>:/ { printf "0x%s\n",$1 }}]
verbose -log "addr_of_libloopfunc: $addr_of_libloopfunc"

# Obtain a list of all process* probe types from stap
verbose -log "eval exec stap --dump-probe-types | grep ^process"
catch {eval exec stap --dump-probe-types | grep ^process} res_stap
set all_probe_types [split $res_stap "\n"]

# Initialize each probe type to untested
foreach probe_type $all_probe_types {
    verbose -log $probe_type
    set tested($probe_type) 0
}
if {[llength all_probe_types] > 0} {
    pass "$test: Obtain list of supported probe types"
} else {
    fail "$test: Obtain list of supported probe types"
    untested "$test Tests"
    return
}

# Now run the tests
test_probes stapusr
test_probes stapsys
#test_probes stapdev

# Generate a failure for each untested probe type
foreach probe_type $all_probe_types {
    set status $tested($probe_type)
    if {$status != 0} {
	pass "$test: tested: $probe_type"
    } else {
	fail "$test: not tested: $probe_type"
    }
}

# Cleanup
catch {exec rm -f $exepath}
catch {exec rm -f $sopath}
