#!/bin/bash # Compile server for systemtap # # Copyright (C) 2008, 2009 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 unpacks the tar file provided on stdin and uses the information # contained in the unpacked tree to build the requested systemtap kernel module. # This module is then written to stdout. # Catch ctrl-c and other termination signals trap 'terminate' SIGTERM SIGINT # Initialize the environment . `dirname $0`/stap-env #----------------------------------------------------------------------------- # Helper functions. #----------------------------------------------------------------------------- # function: initialization function initialization { # Initialization stap_rc=0 wd=`pwd` # Default options settings p_phase=5 keep_temps=0 # Request file name. zip_client=$1 test "X$zip_client" != "X" || \ fatal "Client request file not specified" test -f $zip_client || \ fatal "Unable to find request file $zip_client" # Temp directory we will be working in tmpdir_server=$2 test "X$tmpdir_server" != "X" || \ fatal "Server temporary directory not specified" test -d $tmpdir_server || \ fatal "Unable to find temporary directory $tmpdir_server" tmpdir_env=`dirname $tmpdir_server` # Signed reponse file name. zip_server=$3 test "X$zip_server" != "X" || \ fatal ".zip archive file not specified" # Make sure the specified .zip file exists. test -f $zip_server || \ fatal "Unable to find .zip archive file $zip_server" # Where is the ssl certificate/key database? ssl_db=$4 test "X$ssl_db" != "X" || \ fatal "SSL certificate database not specified" test -d $ssl_db || \ fatal "Unable to find SSL certificate database $ssl_db" nss_pw=$ssl_db/pw test -f $nss_pw || \ fatal "Unable to find SSL certificate database password file $nss_pw" nss_cert=stap-server touch $tmpdir_server/stdout touch $tmpdir_server/stderr } # function: unpack_request # # Unpack the zip file received from the client and make the contents # available for use when running 'stap' function unpack_request { cd $tmpdir_server # Unpack the zip file. unzip $zip_client > /dev/null || \ fatal "Cannot unpack zip archive $zip_client" # Identify the client's request tree. The zip file should have expanded # into a single directory named to match $stap_tmpdir_prefix_client.?????? # which should now be the only item in the current directory. test "`ls | wc -l`" = 3 || \ fatal "Wrong number of files after expansion of client's zip file" tmpdir_client=`ls | grep $stap_tmpdir_prefix_client.......\$` test "X$tmpdir_client" != "X" || \ fatal "Client zip file did not expand as expected" # Move the client's temp directory to a local temp location local local_tmpdir_client=`mktemp -dt $stap_tmpdir_prefix_server.client.XXXXXX` || \ fatal "Cannot create temporary client request directory " $local_tmpdir_client mv $tmpdir_client/* $local_tmpdir_client rm -fr $tmpdir_client tmpdir_client=$local_tmpdir_client } # function: check_request # # Examine the contents of the request to make sure that they are valid. function check_request { # Work in the temporary directory provided by the client cd $tmpdir_client # Add the necessary info from files in our temporary directory. cmdline=`read_data_file cmdline` test "X$cmdline" != "X" || exit 1 eval parse_options "$cmdline" client_sysinfo=`read_data_file sysinfo` test "X$client_sysinfo" != "X" || exit 1 check_compatibility "$client_sysinfo" "`server_sysinfo`" } # function server_sysinfo # # Generate the server's sysinfo and echo it to stdout function server_sysinfo { if test "X$sysinfo_server" = "X"; then # Add some info from uname sysinfo_server="`uname -rvm`" fi echo "$sysinfo_server" } # function check_compaibility SYSINFO1 SYSINFO2 # # Make sure that systemtap as described by SYSINFO1 and SYSINFO2 are compaible function check_compatibility { # Compatibility is irrelevant if the request is not for phase 5 activity. test $p_phase -lt 5 && return # TODO: This needs work # - Make sure the linux kernel matches exactly local sysinfo1=$1 local sysinfo2=$2 if test "$sysinfo1" != "$sysinfo2"; then error "System configuration mismatch" error " client: $sysinfo1" fatal " server: $sysinfo2" fi } # function: read_data_file PREFIX # # Find a file whose name is '$1' and whose first line # contents are '$1: .*'. Read and echo the data. function read_data_file { test -f $1 || \ fatal "Data file $1 not found" # Open the file exec 3< $1 # Verify the first line of the file. read <&3 line="$REPLY" data=`expr "$line" : "$1: \\\(.*\\\)"` if test "X$data" = "X"; then fatal "Data in file $1 is incorrect" return fi # Close the file exec 3<&- # Now read the entire file. cat $1 | sed "s/$1: //" } # 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. function parse_options { while test $# != 0 do advance_p=0 dash_seen=0 # Start of a new token. first_token=$1 until test $advance_p != 0 do # Identify the next option first_char=`expr "$first_token" : '\(.\).*'` 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. Remove the dash. first_token=`expr "$first_token" : '-\(.*\)'` dash_seen=1 first_char=`expr "$first_token" : '\(.\).*'` fi fi if test $dash_seen = 0; then # The dash has not been seen. This is either the script file # name or an arument 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 fi advance_p=$(($advance_p + 1)) break fi fi # We are at the start of an option. Look at the first character. case $first_char in c) get_arg $first_token "$2" ;; D) get_arg $first_token $2 ;; e) get_arg $first_token "$2" process_e "$stap_arg" ;; I) get_arg $first_token $2 ;; k) keep_temps=1 ;; l) get_arg $first_token $2 ;; m) get_arg $first_token $2 ;; o) get_arg $first_token $2 ;; p) get_arg $first_token $2 process_p $stap_arg ;; r) get_arg $first_token $2 ;; R) get_arg $first_token $2 ;; s) get_arg $first_token $2 ;; x) get_arg $first_token $2 ;; *) # An unknown flag. Ignore it. ;; esac if test $advance_p = 0; then # Just another flag character. Consume it. first_token=`expr "$first_token" : '.\(.*\)'` if test "X$first_token" = "X"; then advance_p=$(($advance_p + 1)) fi fi done # Consume the arguments we just processed. while test $advance_p != 0 do shift advance_p=$(($advance_p - 1)) done done } # function: get_arg FIRSTWORD SECONDWORD # # Collect an argument to the given option function get_arg { # Remove first character. Advance to the next token, if the first one # is exhausted. local first=`expr "$1" : '.\(.*\)'` if test "X$first" = "X"; then shift advance_p=$(($advance_p + 1)) first=$1 fi stap_arg="$first" advance_p=$(($advance_p + 1)) } # function: process_e ARGUMENT # # Process the -e flag. function process_e { if test "X$e_script" = "X"; then e_script="$1" script_file= fi } # function: process_p ARGUMENT # # Process the -p flag. function process_p { if test $1 -ge 1 -a $1 -le 5; then p_phase=$1 fi } # function: call_stap # # Call 'stap' with the options provided. Don't run past phase 4. function call_stap { # Invoke systemtap. # Use -k so we can return results to the client # Limit to -p4. i.e. don't run the module cd $tmpdir_client if test $p_phase -gt 4; then server_p_phase=4 else server_p_phase=$p_phase fi eval ${stap_exec_prefix}stap "$cmdline" -k -p $server_p_phase \ >> $tmpdir_server/stdout \ 2>> $tmpdir_server/stderr stap_rc=$? } # function: create_response # # Add information to the server's temp directory representing the response # to the client. function create_response { cd $tmpdir_server # Get the name of the stap temp directory, which was kept, from stderr. tmpdir_line=`cat stderr | grep "Keeping temp"` tmpdir_stap=`expr "$tmpdir_line" : '.*"\(.*\)".*'` # Remove the message about keeping the stap temp directory from stderr, unless # the user did request to keep it. if test "X$tmpdir_stap" != "X"; then if test $keep_temps != 1; then sed -i "/^Keeping temp/d" stderr fi # Add the contents of the stap temp directory to the server output directory ln -s $tmpdir_stap `basename $tmpdir_stap` fi # If the user specified -p5, remove the name of the kernel module from stdout. if test $p_phase = 5; then sed -i '/\.ko$/d' stdout fi # The return status of the stap command. echo -n $stap_rc > rc } # function: package_response # # Package the server's temp directory into a form suitable for sending to the # client. function package_response { cd $tmpdir_env # Compress the server's temporary directory into a .zip archive. (rm $zip_server && zip -r $zip_server `basename $tmpdir_server` > /dev/null) || \ fatal "zip of request tree, $tmpdir_server, failed" } # function: fatal [ MESSAGE ] # # Fatal error # Prints its arguments to stderr and exits function fatal { echo "$0: ERROR:" "$@" >> $tmpdir_server/stderr echo -n 1 > $tmpdir_server/rc package_response cleanup exit 1 } # Non fatal error # Prints its arguments to stderr but does not exit function error { echo "$0: ERROR:" "$@" >> $tmpdir_server/stderr } # 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_server rm -fr $tmpdir_client rm -fr $tmpdir_stap fi } # function: terminate # # Terminate gracefully. function terminate { # Clean up cleanup exit 1 } #----------------------------------------------------------------------------- # Beginning of main line execution. #----------------------------------------------------------------------------- initialization "$@" unpack_request check_request call_stap create_response package_response cleanup exit 0