#!/bin/bash

# Compile server client for systemtap
#
# Copyright (C) 2008-2010 Red Hat Inc.
#
# This file is part of systemtap, and is free software.  You can
# redistribute it and/or modify it under the terms of the GNU General
# Public License (GPL); either version 2, or (at your option) any
# later version.

# This script examines the systemtap command line and packages the files and
# information needed to execute the command. This is then sent to a trusted
# systemtap server which will process the request and return the resulting
# kernel module (if requested) and any other information generated by the
# request. If a kernel module is generated, this script will load the module
# and execute it using 'staprun', if requested.

# Catch ctrl-c and other termination signals
trap 'terminate' SIGTERM
trap 'interrupt' SIGINT
trap 'ignore_signal' SIGHUP SIGPIPE

# Initialize the environment
. ${PKGLIBEXECDIR}stap-env

#-----------------------------------------------------------------------------
# Helper functions.
#-----------------------------------------------------------------------------
# function: initialization
function initialization {
    our_host_name=`expr "$HOSTNAME" : "\\\([a-zA-Z0-9-]*\\\).*"`
    our_domain_name=`expr "$HOSTNAME" : "$our_host_name\\\(.*\\\)"`

    rc=0
    wd=`pwd`
    umask 0

    # Default location for server certificates if we're not root
    # Must be owned by us.
    local uid
    if test $EUID != 0; then
	if test -e $stap_user_ssl_db/client; then
	    if check_db $stap_user_ssl_db/client $EUID $USER; then
		local_ssl_dbs=$stap_user_ssl_db/client
	    fi
	fi
    fi
    # Additional location for all users. Must be owned by root.
    if test -e $stap_root_ssl_db/client; then
	if check_db $stap_root_ssl_db/client 0 root; then
	    public_ssl_dbs=$stap_root_ssl_db/client
	fi
    fi

    # Default options settings
    p_phase=5
    v_level=0
    keep_temps=0
    m_name=
    module_name=stap_$$
    uname_r="`uname -r`"
    arch=`stap_get_arch`

    # Default variable settings
    find_all=

    # Create a temporary directory to package things in
    # Do this before parsing the command line so that there is a place
    # to put -I and -R directories.
    tmpdir_client=`mktemp -dt $stap_tmpdir_prefix_client.XXXXXX` || \
	fatal "Cannot create temporary directory " $tmpdir_client
    tmpdir_env=`dirname $tmpdir_client`

    session_only_prompt_done=0
}

# function: parse_options [ STAP-OPTIONS ]
#
# Examine the command line. We need not do much checking, but we do need to
# parse all options in order to discover the ones we're interested in.
# The server will take care of most situations and return the appropriate
# output.
#
function parse_options {
    # Each command line argument will be written to its own file within the
    # request package.
    argc=1
    packed_options='-'
    arg_subst=

    while test $# != 0
    do
	advance=0
	dash_seen=0
	client_arg=0

        # Start of a new token.
	first_token="$1"
	until test $advance != 0
	do
            # Identify the next option
	    first_char="${first_token:0:1}"
	    second_char=
	    if test $dash_seen = 0; then
		if test "$first_char" = "-"; then
		    if test "$first_token" != "-"; then
	                # It's not a lone dash, so it's an option.
			# Is it a long option (i.e. --option)?
			second_char="${first_token:1:1}"
			if test "X$second_char" = "X-"; then
			    case "$first_token" in
				--ssl=*)
				    process_ssl "$first_token"
				    ;;
				--server=*)
				    process_server "$first_token"
				    ;;
				*)
		                    # An unknown or unimportant option. Ignore it.
				    ;;
			    esac
			    advance=$(($advance + 1))
			    break
			fi
	                # It's not a lone dash, or a long option, so it's a short option string.
			# Remove the dash.
			first_token="${first_token:1}"
			dash_seen=1
			first_char="${first_token:0:1}"
		    fi
		fi
		if test $dash_seen = 0; then
	            # The dash has not been seen. This is either the script file
	            # name or an argument to be passed to the probe module.
	            # If this is the first time, and -e has not been specified,
	            # then it could be the name of the script file.
		    if test "X$e_script" = "X" -a "X$script_file" = "X"; then
			script_file="$first_token"
			script_file_argc=$argc
		    fi
		    advance=$(($advance + 1))
		    break
		fi
	    fi

            # We are at the start of an option. Look at the first character.
	    case $first_char in
		a)
		    get_arg "$first_token" "$2"
		    process_a "$stap_arg"
		    ;;	
		B)
		    get_arg "$first_token" "$2"
		    ;;	
		c)
		    get_arg "$first_token" "$2"
		    process_c "$stap_arg"
		    ;;
		D)
		    get_arg "$first_token" "$2"
		    ;;
		e)
		    get_arg "$first_token" "$2"
		    process_e "$stap_arg"
		    ;;
		I)
		    get_arg "$first_token" "$2"
		    process_I "$stap_arg"
		    ;;	
		k)
		    keep_temps=1
		    ;;
		l)
		    get_arg "$first_token" "$2"
		    p_phase=2
		    ;;
		L)
		    get_arg "$first_token" "$2"
		    p_phase=2
		    ;;
		m)
		    get_arg "$first_token" "$2"
		    process_m "$stap_arg"
		    ;;
		o)
		    get_arg "$first_token" "$2"
		    process_o "$stap_arg"
		    ;;
		p)
		    get_arg "$first_token" "$2"
		    process_p "$stap_arg"
		    ;;
		r)
		    get_arg "$first_token" "$2"
		    process_r "$stap_arg"
		    ;;	
		R)
		    get_arg "$first_token" "$2"
		    process_R "$stap_arg"
		    ;;	
		s)
		    get_arg "$first_token" "$2"
		    ;;	
		S)
		    get_arg "$first_token" "$2"
		    ;;	
		v)
		    v_level=$(($v_level + 1))
		    ;;
		x)
		    get_arg "$first_token" "$2"
		    ;;
		*)
		    # An unknown or unimportant flag.
		    ;;
	    esac

	    if test $advance = 0; then
	        # Just another flag character. Consume it.
		first_token="${first_token:1}"
		if test "X$first_token" = "X"; then
		    advance=$(($advance + 1))
		fi
	    fi
	done

        # Consume the arguments we just processed.
	while test $advance != 0; do
	    # Does the final argument file contain a client-side file
	    # name which must be changed to a server-side name?
	    local arg
	    if test "X$arg_subst" != "X" -a $advance = 1; then
		arg="$arg_subst"
		arg_subst=
	    else
		arg="$1"
	    fi

	    # If it's not client-only argument,
	    # place the argument in a numbered file within our temp
	    # directory.
	    # o We don't write a newline at the end, since newline could be
	    #   part of the argument.
	    # o We add an X to the beginning of the file
	    #   in order to avoid having 'echo' interpret the output as
	    #   its own option. We then remove the X.
	    #   There must be a better way.
	    if test $client_arg = 0; then
		echo -n "X$arg" > "$tmpdir_client/argv$argc"
		sed -i "s|^X||" "$tmpdir_client/argv$argc"
		argc=$(($argc + 1))
	    fi

	    # Get the next argument.
	    shift
	    advance=$(($advance - 1))
	    packed_options='-'
	done
    done

    # If the script file was given and it's not '-', then replace it with its
    # client-temp-name in its argument file.
    if test "X$script_file" != "X"; then
	local local_name
	if test "$script_file" != "-"; then
	    generate_client_temp_name "$script_file"
	    local_name="$client_temp_name"
	else
	    local_name="-"
	fi
	echo -n "script/$local_name" > "$tmpdir_client/argv$script_file_argc"
    fi

    # Processing based on final options settings
    # Complete the list of local certificate databases
    local_ssl_dbs="$additional_local_ssl_dbs $local_ssl_dbs"

    # We must have at least one usable certificate database.
    test "X$local_ssl_dbs" != "X " -o "X$public_ssl_dbs" != "X" || \
	fatal "No usable certificate databases found"
}

# function: get_arg FIRSTWORD SECONDWORD
#
# Collect an argument to the given option
function get_arg {
    # Remove first character.
    local opt="${1:0:1}"
    local first="${1:1}"
    packed_options="${packed_options}$opt"

    # Advance to the next token, if the first one is exhausted.
    if test "X$first" = "X"; then
	advance=$(($advance + 1))
	first="$2"
    fi

    stap_arg="$first"
    test "X$first" != "X" && advance=$(($advance + 1))
}

# function: process_ssl ARGUMENT
#
# Process the --ssl option.
function process_ssl {
    client_arg=1
    local db="${1:6}"

    test "X$db" != "X" || \
	fatal "Missing argument to --ssl"

    check_db "$db" || return

    additional_local_ssl_dbs="$additional_local_ssl_dbs $db"
}

# function: process_server ARGUMENT
#
# Process the --server option.
function process_server {
    client_arg=1
    local spec="${1:9}"

    test "X$spec" != "X" || \
	fatal "Missing argument to --server"

    specified_servers="$specified_servers $spec"
}

# function: process_c ARGUMENT
#
# Process the -c flag.
function process_c {
    c_cmd="$1"
}

# function: process_e ARGUMENT
#
# Process the -e flag.
function process_e {
    # Only the first -e option is recognized and it overrides any script file name
    # which may have already been identified.
    if test "X$e_script" = "X"; then
	e_script="$1"
	script_file=
    fi
}

# function: process_I ARGUMENT ORIGINAL_ARGUMENT
#
# Process the -I flag.
function process_I {
    test "X$1" = "X" && return
    test "${1:0:1}" = "
" && return
    include_file_or_directory tapsets "$1"
    if test $advance = 1; then
	arg_subst="${packed_options}tapsets/$included_name"
    else
	arg_subst="tapsets/$included_name"
    fi
}

# function: process_m ARGUMENT
#
# Process the -m flag.
function process_m {
    module_name="$1"
    m_name="$1"
}

# function: process_o ARGUMENT
#
# Process the -o flag.
function process_o {
    stdout_redirection="$1"
}

# function: process_p ARGUMENT
#
# Process the -p flag.
function process_p {
    p_phase="$1"
}

# function: process_r ARGUMENT
#
# Process the -r flag.
function process_r {
    local first_char="${1:0:1}"

    if test "$first_char" = "/"; then # fully specified path
        kernel_build_tree="$1"
        version_file_name="$kernel_build_tree/include/config/kernel.release"
        # The file include/config/kernel.release within the
        # build tree is used to pull out the version information
	release=`cat "$version_file_name" 2>/dev/null`
	if test "X$release" = "X"; then
	    fatal "Missing $version_file_name"
	    return
	fi
    else
	# kernel release specified directly
	release="$1"
    fi

    if test "X$release" != "X$uname_r"; then
	uname_r="$release"
	find_all="--all"
    fi
}

# function: process_a ARGUMENT
#
# Process the -a flag.
function process_a {
    if test "X$1" != "X$arch"; then
	arch="$1"
	find_all="--all"
    fi
}

# function: process_R ARGUMENT ORIGINAL_ARGUMENT
#
# Process the -R flag.
function process_R {
    test "X$1" = "X" && return
    test "${1:0:1}" = "
" && return
    include_file_or_directory runtime "$1"
    if test $advance = 1; then
	arg_subst="${packed_options}runtime/$included_name"
    else
	arg_subst="runtime/$included_name"
    fi
}

# function: include_file_or_directory PREFIX NAME
#
# Include the given file or directory in the client's temporary
# tree to be sent to the server and save it's name in the variable
# included_name. We use a global variable instread of echoing the
# result since the use of `include_file_or_directory` loses a trailing
# newline.
function include_file_or_directory {
    # Add a symbolic link of the named file or directory to our temporary
    # directory, but only if the file or directory exists.
    generate_client_temp_name "$2"
    local local_name="$client_temp_name"
    included_name="$local_name"
    test -e "/$local_name" || return

    local local_dirname=`dirname "$local_name"`
    mkdir -p "$tmpdir_client/$1/$local_dirname" || \
	fatal "Could not create $tmpdir_client/$1/$local_dirname"
    ln -s "/$local_name" "$tmpdir_client/$1/$local_name" || \
	fatal "Could not link $tmpdir_client/$1/$local_name to /$local_name"
}

# function: generate_client_temp_name NAME
#
# Generate the name to be used for the given file/directory relative to the
# client's temporary directory and stores it in the variable
# client_temp_name. We use a global variable instread of echoing the
# result since the use of `generate_client_temp_name` loses a trailing
# newline.
function generate_client_temp_name {
    # Transform the name into a fully qualified path name
    local full_name="$1"
    test "${full_name:0:1}" != "/" && full_name="$wd/$full_name"

    # The same name without the initial / or trailing /
    local local_name="${full_name:1}"
    test "${local_name: -1:1}" = "/" && local_name="${local_name:0:$((${#local_name}-1))}"

    client_temp_name="$local_name"
}

# function: create_request
#
# Add information to the client's temp directory representing the request
# to the server.
function create_request {
    # Work in our temporary directory
    cd $tmpdir_client

    if test "X$script_file" != "X"; then
	if test "$script_file" = "-"; then
	    mkdir -p $tmpdir_client/script || \
		fatal "Cannot create temporary directory " $tmpdir_client/script
	    cat > "$tmpdir_client/script/$script_file"
	else
	    include_file_or_directory script "$script_file"
	fi
    fi

    # Add the necessary info to special files in our temporary directory.
    echo "sysinfo: `client_sysinfo`" > sysinfo
}

# function client_sysinfo
#
# Generate the client's sysinfo and echo it to stdout
function client_sysinfo {
    echo "$uname_r $arch"
}

# function: package_request
#
# Package the client's temp directory into a form suitable for sending to the
# server.
function package_request {
    # Package up the temporary directory into a zip file
    cd $tmpdir_env

    local tmpdir_client_base=`basename $tmpdir_client`
    zip_client=$tmpdir_env/`mktemp $tmpdir_client_base.zip.XXXXXX` || \
	fatal "Cannot create temporary file " $zip_client

    cd $tmpdir_client
    (rm -f $zip_client && zip -r $zip_client * > /dev/null) || \
	fatal "zip of request tree, $tmpdir_client, failed"
}

# function: unpack_response
#
# Unpack the zip file received from the server and make the contents available
# for printing the results and/or running 'staprun'.
function unpack_response {
    tmpdir_server=`mktemp -dt $stap_tmpdir_prefix_client.server.XXXXXX` || \
	fatal "Cannot create temporary file " $tmpdir_server

    # Unpack the server output directory
    unzip -d $tmpdir_server $zip_server > /dev/null || \
	fatal "Cannot unpack server response, $zip_server"

    # Check the contents of the directory. It should contain:
    # 1) a file called stdout
    # 2) a file called stderr
    # 3) a file called rc
    # 4) optionally a directory named to match stap??????
    local num_files=`ls $tmpdir_server | wc -l`
    test $num_files = 4 -o $num_files = 3 || \
	fatal "Wrong number of files in server's temp directory"
    test -f $tmpdir_server/stdout || \
	fatal "`pwd`/$tmpdir_server/stdout does not exist or is not a regular file"
    test -f $tmpdir_server/stderr || \
	fatal "`pwd`/$tmpdir_server/stderr does not exist or is not a regular file"
    test -f $tmpdir_server/rc || \
	fatal "`pwd`/$tmpdir_server/rc does not exist or is not a regular file"

    # See if there is a systemtap temp directory.  There should be at least an empty one.
    # ls -l $tmpdir_server
    tmpdir_stap=`cd $tmpdir_server && ls | grep stap......\$ 2>/dev/null`
    if test "X$tmpdir_stap" != "X"; then
	test -d $tmpdir_server/$tmpdir_stap || \
	    fatal "$tmpdir_server/$tmpdir_stap is not a directory"

        # Move the systemtap temp directory to a local temp location, if -k
        # was specified.
	if test $keep_temps = 1; then
	    local local_tmpdir_stap=`mktemp -dt stapXXXXXX` || \
		fatal "Cannot create temporary directory " $local_tmpdir_stap
	    mv $tmpdir_server/$tmpdir_stap/* $local_tmpdir_stap 2>/dev/null
	    rm -fr $tmpdir_server/$tmpdir_stap

	    # Correct the name of the temp directory in the server's stderr output
	    sed -i "s,^Keeping temporary directory.*,Keeping temporary directory \"$local_tmpdir_stap\"," $tmpdir_server/stderr
	    tmpdir_stap=$local_tmpdir_stap
	else
	    tmpdir_stap=$tmpdir_server/$tmpdir_stap
            # Make sure we own the systemtap temp directory if we are root.
	    test $EUID = 0 && chown $EUID:$EUID $tmpdir_stap
	fi
    fi

    if test $keep_temps = 0; then
      # Remove the output line due to the synthetic server-side -k
      sed -i "/^Keeping temporary directory.*/ d" $tmpdir_server/stderr
    fi

    if test $p_phase = 5; then
      # Remove the output line due to the synthetic server-side -p4
      sed -i "/^.*\.ko$/ d" $tmpdir_server/stdout
    fi
}

# function: find_and_connect_to_server
#
# Find and establish connection with a compatible stap server.
function find_and_connect_to_server {
    local num_servers=0

    # Make a place to receive the response file.
    zip_server=`mktemp -t $stap_tmpdir_prefix_client.server.zip.XXXXXX` || \
	fatal "Cannot create temporary file " $zip_server

    # If servers were specified on the command line, then try them
    # in sequence. Don't try any other servers.
    if test "X$specified_servers" != "X"; then
	for server in $specified_servers; do
	    num_servers=$(($num_servers + 1))

	    # If the server is completely specified, (i.e. server:port),
            # then try it directly.
	    port=`expr "$server" : '.\+:\([0-9]\+\)'`
            if test "X$port" != "X"; then
		name=`expr "$server" : '\(.\+\):[0-9]\+'`

                # If we have been given an ip address, then try to resolve it to a name.
                # If we have been given a name, try to resolve the full name.
                # The full name is needed in order to validate the server's certificate.
                address=`expr "$name" : '\([0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+\)'`
		if test "X$address" = "X"; then
		    # We've been given a host name
		    full_name=`nslookup $name | awk '/^Name\:/ {print $2}'`
		    if test "X$full_name" != "X"; then
			name=$full_name
		    fi
		else
		    # We've been given an ip address.
		    name=`nslookup $address | awk '/in-addr\.arpa/ {print $4}'`
		    name=`expr "$name" : '\(.*\)\.'`
                    if test "X$name" = "X"; then
			echo "Cannot resolve ip address $address" >> "$tmpdir_client/connect"
			continue
		    fi
		fi

		# Now try to contact the given server.
		send_receive $name $port && return
		continue
	    fi

	    # Otherwise select the matching server from the available servers
	    # and use the port it is advertizing.
	    #
	    # Have we been given an ip address? If so, just use it.
	    address=`expr "$server" : '\([0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+\)'`
	    if test "X$address" = "X"; then
		# We have not been given an ip address. Try to resolve it as a host name.
		if test "X$server" = "Xlocalhost"; then
		    # We don't want the address of the loopback interface here. Avahi will present
		    # the actual ip address.
		    server=$our_host_name$our_domain_name
		fi
		address=`nslookup $server | awk '/^Address\:[ \t][0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {print $2}'`
                if test "X$address" = "X"; then
		    echo "Cannot resolve server $server" >> "$tmpdir_client/connect"
		    continue
		fi
	    fi

	    ${stap_pkglibexecdir}stap-find-servers $find_all | grep $address > "$tmpdir_client/servers"

	    if test `wc -l "$tmpdir_client/servers" | awk '{print $1}'` = "0"; then
		warning "No server is available on $server" 2>> "$tmpdir_client/connect"
		continue
	    fi

            choose_server && return
	done
    else
        # No servers specified. Find available servers and choose one of them.
	# Remember which ssl certificate database was used to authenticate the chosen
        # server.
	${stap_pkglibexecdir}stap-find-servers $find_all > "$tmpdir_client/servers"
        choose_server && return
	num_servers=`wc -l "$tmpdir_client/servers" | awk '{print $1}'`
    fi

    if test $num_servers = 0; then
	fatal "Unable to find a server"
    fi

    test -f "$tmpdir_client/connect" && cat "$tmpdir_client/connect" >&2
    fatal "Unable to connect to a server"
}

# function: choose_server
#
# Examine each line from "$tmpdir_client/servers" and attempt to connect to
# each server specified until successful.
function choose_server {
    local name ip port remain

    while read -u3 name ip port remain
    do
	if test "X$name" = "X"; then
	    fatal "Server name not provided by avahi"
	fi

#	if test "X$ip" = "X"; then
#	    fatal "Server ip address not provided by avahi"
#	fi

	if test "X$port" = "X"; then
	    fatal "Server port not provided by avahi"
	fi

	# Does the server build for the kernel release and architecture that we want?
	sysinfo=`expr "$remain" : "'sysinfo=\\\(.*\\\)'"`
	test "X$sysinfo" != "X$uname_r $arch" && continue

	send_receive $name $port && return
    done 3< "$tmpdir_client/servers"

    # Could not connect to a server
    return 1
}

# function: send_receive SERVER PORT
#
# Connect to the server, send the request and receive the response.
function send_receive {
    local server="$1"
    local port="$2"

    # The server must match the dns name on the certificate
    # and must be 'localhost' if the server is on the local host.
    local server_host_name=`expr "$server" : "\\\([a-zA-Z0-9-]*\\\).*"`
    local server_domain_name=`expr "$server" : "$server_host_name\\\(.*\\\)"`

    if test "X$server_domain_name" = "X.local"; then
	server_domain_name=$our_domain_name
    fi
    if test "X$server_host_name$server_domain_name" = "Xlocalhost$our_domain_name"; then
	server=localhost
    elif test "X$server_host_name$server_domain_name" = "X$our_host_name$our_domain_name"; then
	server=localhost
    else
	server=$server_host_name$server_domain_name
    fi

    # Try to connect using each of the given local certificate databases in turn
    # for verification.
    local rc
    for ssl_db in $local_ssl_dbs
    do
        # Send the request and receive the response using stap-client-connect
	attempt_connection -i $zip_client -o $zip_server -d $ssl_db -p $port -h $server && return

	# Try the next database, but give the server a chance to reset.
	sleep 1
    done

    # Next, try the public certificate databases.
    for ssl_db in $public_ssl_dbs
    do
        # Send the request and receive the response using stap-client-connect
	attempt_connection -i $zip_client -o $zip_server -d $ssl_db -p $port -h $server && return

	# Try the next database, but give the server a chance to reset.
	sleep 1
    done

    # Could not connect using any of the certificate databases
    return 1
}

# function: attempt_connection ARGS
#
# Attempt connection with the given server. Give the user a chance to
# trust the server if it is not already trusted
function attempt_connection {
    echo "Attempting connection with $server:$port using certificate database in '$ssl_db'" >> "$tmpdir_client/connect"

    # Send the request and receive the response using stap-client-connect
    ${stap_pkglibexecdir}stap-client-connect "$@" >> "$tmpdir_client/connect" 2>&1 &
    wait '%${stap_pkglibexecdir}stap-client-connect'
    local rc=$?
    test $rc = 0 && return

    # The connection failed. If it was because the server is not trusted, give
    # the user a chance to decide whether to trust the server anyway.
    # The prompt will not be printed and the read will quickly timeout if
    # stdin is not from a terminal.
    if test $rc = 2; then
	# Output any connection messages generated thus far
	cat "$tmpdir_client/connect" >&2
	rm  "$tmpdir_client/connect"

	local response
	local prompt="The server at $server:$port is not trusted based on the certificate database in '$ssl_db'
"
	if test $session_only_prompt_done = 0; then
	    session_only_prompt_done=1
	    prompt="${prompt}Trust this server for for this session only? [y/N] "
	    read -t 30 -p "$prompt" response || echo n
	    if test "$response" = "y" -o "$response" = "Y"; then
		${stap_pkglibexecdir}stap-client-connect "$@" -t session >> "$tmpdir_client/connect" 2>&1 &
		wait '%${stap_pkglibexecdir}stap-client-connect'
		test $? = 0 && return
		return 1 # Connection failed
	    fi
	    prompt=
	fi
	if test "$ssl_db" = "$stap_root_ssl_db/client"; then
	    prompt="${prompt}Adding this server's certificate to this database will make this server trusted by all users on the local host.
"
	fi
	prompt="${prompt}Add this server's certificate to '$ssl_db'? [y/N] "
	read -t 30 -p "$prompt" response || echo n
	if test "$response" = "y" -o "$response" = "Y"; then
	    ${stap_pkglibexecdir}stap-client-connect "$@" -t permanent >> "$tmpdir_client/connect" 2>&1 &
	    wait '%${stap_pkglibexecdir}stap-client-connect'
	    test $? = 0 && return
	fi
    fi

    # Connection failed
    return 1
}

# function: process_response
#
# Write the stdout and stderr from the server to stdout and stderr respectively.
function process_response {
    # Pick up the results of running stap on the server.
    cd $tmpdir_server
    rc=`cat rc`

    if test $p_phase -ge 4; then
	if test -f $tmpdir_stap/*.ko; then
	    if test $p_phase = 4 -o "X$m_name" != "X"; then
		cp -p $tmpdir_stap/*.ko $wd/$module_name.ko
		test -f $tmpdir_stap/*.sgn && cp -p $tmpdir_stap/*.sgn $wd/$module_name.ko.sgn
	    else
		module_name=`ls $tmpdir_stap/*.ko`
		module_name=`expr "$module_name" : '\(.*\)\.ko'`
	    fi
	elif test "X$script_file" != "X" -o "X$e_script" != "X"; then
	    if test "X$rc" != "X" -a $rc = 0; then
		stream_output
		fatal "no module returned by the server"
	    fi
	fi
    fi

    # Change the name of the temp directory and module name in stdout and stderr
    sed -i "s,stap_[0-9]\+,$module_name,g" $tmpdir_server/stdout
    sed -i "s,stap_[0-9]\+,$module_name,g" $tmpdir_server/stderr
    sed -i "s,into \".*$module_name,into \"$module_name,g" $tmpdir_server/stdout
    sed -i "s,into \".*$module_name,into \"$module_name,g" $tmpdir_server/stderr

    # Output stdout and stderr as directed
    stream_output
}

# function: stream_output
#
# Output stdout and stderr as directed
function stream_output {
    cd $tmpdir_server
    cat stderr >&2
    cat stdout
}

# function: maybe_call_staprun
#
# Call staprun using the module returned from the server, if requested.
function maybe_call_staprun {
    if test $rc != 0; then
	# stap run on the server failed, so don't bother
	return
    fi

    if test $p_phase -ge 4; then
        # There should be a systemtap temporary directory.
	if test "X$tmpdir_stap" = "X"; then
	    # OK if no script specified
	    if test "X$e_script" != "X" -o "X$script_file" != "X"; then
		fatal "systemtap temporary directory is missing in server response"
	    fi
	    return
	fi

	if test $p_phase = 5; then
	    test $v_level -gt 0 && echo "Pass 5: starting run." >&2

	    # We have a module. Try to run it
	    # If a -c command was specified, pass it along.
	    if test "X$c_cmd" != "X"; then
		staprun_opts="-c '$c_cmd'"
	    fi

	    # The -v level will be one less than what was specified
	    # for us.
	    for ((vl=$((v_level - 1)); $vl > 0; --vl))
	    do
		staprun_opts="$staprun_opts -v"
	    done

	    # if -o was specified, pass it along
	    if test "X$stdout_redirection" != "X"; then
		staprun_opts="$staprun_opts -o $stdout_redirection"
	    fi

	    # Run it from our original working directory
	    cd $wd

	    # Run it in the background and wait for it. This
	    # way any signals sent to us can be caught.
	    if test $v_level -ge 2; then
		echo "running `staprun_PATH` $staprun_opts $module_name.ko" >&2
	    fi
	    eval `staprun_PATH` "$staprun_opts" $module_name.ko
	    rc=$?

	    # Wait until the job actually disappears so that its output is complete.
	    while jobs '%?staprun' >/dev/null 2>&1
	    do
		sleep 1
	    done

	    test $v_level -gt 0 && echo "Pass 5: run completed in 0usr/0sys/0real ms." >&2
	fi
    fi
}

# function: staprun_PATH
#
# Compute a PATH suitable for running staprun.
function staprun_PATH {
    # If $SYSTEMTAP_STAPRUN is set, then use that
    if test "X$SYSTEMTAP_STAPRUN" != "X"; then
	echo $SYSTEMTAP_STAPRUN
	return
    fi

    # Otherwise, if there is an exec_prefix, then use it.
    if test "X$stap_exec_prefix" != "X"; then
	echo ${stap_exec_prefix}staprun
	return
    fi

    # Otherwise, we have been called by the dejagnu test harness as 'stap'
    # and we are the first 'stap' on the path. Since staprun may call
    # 'stap', remove the PATH component where we live from the PATH in order to
    # avoid recursion.
    local first_stap=`which stap`
    local PATH_component=`dirname $first_stap`
    echo "PATH=$PATH staprun" | sed "s,$PATH_component,,g"
}

# function: check_db DBNAME [ EUID USER ]
#
# Check the security of the given database directory.
function check_db {
    local dir="$1"
    local euid="$2"
    local user="$3"
    local rc=0

    # Check that we have been given a directory
    if ! test -e $dir; then
	warning "Certificate database '$dir' does not exist"
	return 1
    fi
    if ! test -d $dir; then
	warning "Certificate database '$dir' is not a directory"
	return 1
    fi

    # If euid has been specified, then this directory must be owned by that
    #  user.
    if test "X$euid" != "X"; then
	local ownerid=`stat -c "%u" $dir`
	if test "X$ownerid" != "X$euid"; then
	    warning "Certificate database '$dir' must be owned by $user"
	    rc=1
	fi
    fi

    # Check that we can read the directory
    if ! test -r $dir; then
	warning "Certificate database '$dir' is not readble"
	rc=1
    fi

    # Check the access permissions of the directory
    local perm=0`stat -c "%a" $dir`
    if test $((($perm & 0400) == 0400)) = 0; then
	warning "Certificate database '$dir' should be readable by the owner"
    fi
    if test $((($perm & 0200) == 0200)) = 0; then
	warning "Certificate database '$dir' should be writeable by the owner"
    fi
    if test $((($perm & 0100) == 0100)) = 0; then
	warning "Certificate database '$dir' should be searchable by the owner"
    fi
    if test $((($perm & 0040) == 0040)) = 0; then
	warning "Certificate database '$dir' should be readable by the group"
    fi
    if test $((($perm & 0020) == 0020)) = 1; then
	warning "Certificate database '$dir' must not be writable by the group"
	rc=1
    fi
    if test $((($perm & 0010) == 0010)) = 0; then
	warning "Certificate database '$dir' should be searchable by the group"
    fi
    if test $((($perm & 0004) == 0004)) = 0; then
	warning "Certificate database '$dir' should be readable by others"
    fi
    if test $((($perm & 0002) == 0002)) = 1; then
	warning "Certificate database '$dir' must not be writable by others"
	rc=1
    fi
    if test $((($perm & 0001) == 0001)) = 0; then
	warning "Certificate database '$dir' should be searchable by others"
    fi

    # Now check the permissions of the critical files.
    check_db_file $dir/cert8.db  $euid $user || rc=1
    check_db_file $dir/key3.db   $euid $user || rc=1
    check_db_file $dir/secmod.db $euid $user || rc=1

    test $rc = 1 && warning "Unable to use certificate database '$dir' due to errors"

    return $rc
}

# function: check_db_file FILENAME [ EUID USER ]
#
# Check the security of the given database file.
function check_db_file {
    local file="$1"
    local rc=0

    # Check that we have been given a file
    if ! test -e $file; then
	warning "Certificate database file '$file' does not exist"
	return 1
    fi
    if ! test -f $file; then
	warning "Certificate database file '$file' is not a regular file"
	return 1
    fi

    # If euid has been specified, then this directory must be owned by that
    #  user.
    if test "X$euid" != "X"; then
	local ownerid=`stat -c "%u" $file`
	if test "X$ownerid" != "X$euid"; then
	    warning "Certificate database file '$file' must be owned by $user"
	    rc=1
	fi
    fi

    # Check that we can read the file
    if ! test -r $file; then
	warning "Certificate database file '$file' is not readble"
	rc=1
    fi

    # Check the access permissions of the file
    local perm=0`stat -c "%a" $file`
    if test $((($perm & 0400) == 0400)) = 0; then
	warning "Certificate database file '$file' should be readable by the owner"
    fi
    if test $((($perm & 0200) == 0200)) = 0; then
	warning "Certificate database file '$file' should be writeable by the owner"
    fi
    if test $((($perm & 0100) == 0100)) = 1; then
	warning "Certificate database file '$file' must not be executable by the owner"
	rc=1
    fi
    if test $((($perm & 0040) == 0040)) = 0; then
	warning "Certificate database file '$file' should be readable by the group"
    fi
    if test $((($perm & 0020) == 0020)) = 1; then
	warning "Certificate database file '$file' must not be writable by the group"
	rc=1
    fi
    if test $((($perm & 0010) == 0010)) = 1; then
	warning "Certificate database file '$file' must not be executable by the group"
	rc=1
    fi
    if test $((($perm & 0004) == 0004)) = 0; then
	warning "Certificate database file '$file' should be readable by others"
    fi
    if test $((($perm & 0002) == 0002)) = 1; then
	warning "Certificate database file '$file' must not be writable by others"
	rc=1
    fi
    if test $((($perm & 0001) == 0001)) = 1; then
	warning "Certificate database file '$file' must not be executable by others"
	rc=1
    fi

    return $rc
}

# function: warning [ MESSAGE ]
#
# Warning error
# Prints its arguments to stderr
function warning {
    echo "$0: WARNING:" "$@" >&2
}

# function: fatal [ MESSAGE ]
#
# Fatal error
# Prints its arguments to stderr and exits
function fatal {
    echo "$0: ERROR:" "$@" >&2
    cleanup
    exit 1
}

# function cleanup
#
# Cleanup work files unless asked to keep them.
function cleanup {
    # Clean up.
    cd $tmpdir_env
    if test $keep_temps != 1; then
	rm -fr $tmpdir_client
	rm -f  $zip_client
	rm -f  $zip_server
	rm -fr $tmpdir_server
    fi
}

# function: terminate
#
# Terminate gracefully.
function terminate {
    # Clean up
    echo "$0: terminated by signal" >&2
    cleanup

    # Kill any running staprun job
    kill -s SIGTERM '%?staprun' 2>/dev/null

    # Kill any stap-client-connect job
    kill -s SIGTERM '%${stap_pkglibexecdir}stap-client-connect' 2>/dev/null

    exit 1
}

# function: interrupt
#
# Pass an interrupt (ctrl-C) to staprun
function interrupt {
    # Kill any stap-client-connect job
    # SIGINT won't do it.
    kill -s SIGTERM '%${stap_pkglibexecdir}stap-client-connect' 2>/dev/null

    # If staprun was not running, then exit.
    cleanup
    exit 1
}

# function: ignore_signal
#
# Called in order to ignore a signal
function ignore_signal {
    :
}

#-----------------------------------------------------------------------------
# Beginning of main line execution.
#-----------------------------------------------------------------------------
initialization
parse_options "$@"
create_request
package_request
find_and_connect_to_server
unpack_response
process_response
maybe_call_staprun
cleanup

exit $rc
