3 # Compile server client for systemtap
5 # Copyright (C) 2008 Red Hat Inc.
7 # This file is part of systemtap, and is free software. You can
8 # redistribute it and/or modify it under the terms of the GNU General
9 # Public License (GPL); either version 2, or (at your option) any
12 # This script examines the systemtap command line and packages the files and
13 # information needed to execute the command. This is then sent to a trusted
14 # systemtap server which will process the request and return the resulting
15 # kernel module (if requested) and any other information generated by the
16 # request. If a kernel module is generated, this script will load the module
17 # and execute it using 'staprun', if requested.
19 # Catch ctrl-c and other termination signals
20 trap 'terminate' SIGTERM
21 trap 'interrupt' SIGINT
23 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # function: configuration
27 function configuration
{
28 tmpdir_prefix_client
=stap.client
29 tmpdir_prefix_server
=stap.server
30 avahi_service_tag
=_stap._tcp
33 # function: initialization
34 function initialization
{
39 # Default options settings
44 # Create a temporary directory to package things in
45 # Do this before parsing the command line so that there is a place
46 # to put -I and -R directories.
47 tmpdir_client
=`mktemp -dt $tmpdir_prefix_client.XXXXXX` || \
48 fatal
"ERROR: cannot create temporary directory " $tmpdir_client
49 tmpdir_env
=`dirname $tmpdir_client`
52 # function: parse_options [ STAP-OPTIONS ]
54 # Examine the command line. We need not do much checking, but we do need to
55 # parse all options in order to discover the ones we're interested in.
56 # The server will take care of most situations and return the appropriate
59 function parse_options
{
68 # Start of a new token.
70 until test $advance_p != 0
72 # Identify the next option
73 first_char
=`expr "$first_token" : '\(.\).*'`
75 if test $dash_seen = 0; then
76 if test "$first_char" = "-"; then
77 if test "$first_token" != "-"; then
78 # It's not a lone dash, so it's an option.
79 # Is it a long option (i.e. --option)?
80 second_char
=`expr "$first_token" : '.\(.\).*'`
81 if test "$second_char" != "-"; then
82 # It's not a lone dash, or a long option, so it's a short option string.
84 first_token
=`expr "$first_token" : '-\(.*\)'`
86 first_char
=`expr "$first_token" : '\(.\).*'`
87 cmdline2
="$cmdline2 -"
91 if test $dash_seen = 0; then
92 # The dash has not been seen. This is either the script file
93 # name, a long argument or an argument to be passed to the probe module.
94 # If this is the first time, and -e has not been specified,
95 # then it could be the name of the script file.
96 if test "X$second_char" = "X-"; then
97 cmdline2
="$cmdline2 $first_token"
98 elif test "X$e_script" = "X" -a "X$script_file" = "X"; then
99 script_file
=$first_token
103 cmdline2
="$cmdline2 '$first_token'"
105 advance_p
=$
(($advance_p + 1))
110 # We are at the start of an option. Look at the first character.
113 get_arg
$first_token "$2"
114 process_c
"$stap_arg"
117 get_arg
$first_token $2
118 cmdline2
="${cmdline2}D '$stap_arg'"
121 get_arg
$first_token "$2"
122 process_e
"$stap_arg"
125 get_arg
$first_token $2
132 get_arg
$first_token $2
133 cmdline2
="${cmdline2}l '$stap_arg'"
136 get_arg
$first_token $2
137 cmdline2
="${cmdline2}m $stap_arg"
140 get_arg
$first_token $2
144 get_arg
$first_token $2
148 get_arg
$first_token $2
149 cmdline2
="${cmdline2}r $stap_arg"
152 get_arg
$first_token $2
156 get_arg
$first_token $2
157 cmdline2
="${cmdline2}s $stap_arg"
160 v_level
=$
(($v_level + 1))
163 get_arg
$first_token $2
164 cmdline2
="${cmdline2}x $stap_arg"
167 # An unknown or unimportant flag. Ignore it, but pass it on to the server.
171 if test $advance_p = 0; then
172 # Just another flag character. Consume it.
173 cmdline2
="$cmdline2$first_char"
174 first_token
=`expr "$first_token" : '.\(.*\)'`
175 if test "X$first_token" = "X"; then
176 advance_p
=$
(($advance_p + 1))
181 # Consume the arguments we just processed.
182 while test $advance_p != 0
185 advance_p
=$
(($advance_p - 1))
189 # If the script file was given and it's not '-', then replace it with its
190 # client-temp-name in the command string.
191 if test "X$script_file" != "X"; then
193 if test "$script_file" != "-"; then
194 local_name
=`generate_client_temp_name $script_file`
196 local_name
=$script_file
198 cmdline
="$cmdline1 script/$local_name $cmdline2"
200 cmdline
="$cmdline1 $cmdline2"
204 # function: get_arg FIRSTWORD SECONDWORD
206 # Collect an argument to the given option
208 # Remove first character.
209 local opt
=`expr "$1" : '\(.\).*'`
210 local first
=`expr "$1" : '.\(.*\)'`
212 # Advance to the next token, if the first one is exhausted.
213 if test "X$first" = "X"; then
215 advance_p
=$
(($advance_p + 1))
219 test "X$first" != "X" || \
220 fatal
"Missing argument to -$opt"
223 advance_p
=$
(($advance_p + 1))
226 # function: process_c ARGUMENT
228 # Process the -c flag.
231 cmdline2
="${cmdline2}c '$1'"
234 # function: process_e ARGUMENT
236 # Process the -e flag.
238 # Only the first -e option is recognized and it overrides any script file name
239 # which may have already been identified.
240 if test "X$e_script" = "X"; then
242 if test "X$script_file" != "X"; then
243 cmdline1
="$cmdline1 $script_file $cmdline2"
248 cmdline2
="${cmdline2}e '$1'"
251 # function: process_I ARGUMENT
253 # Process the -I flag.
255 local local_name
=`include_file_or_directory tapsets $1`
256 test "X$local_name" != "X" ||
return
257 cmdline2
="${cmdline2}I 'tapsets/$local_name'"
260 # function: process_o ARGUMENT
262 # Process the -o flag.
264 stdout_redirection
="> $1"
265 cmdline2
="${cmdline2}o '$1'"
268 # function: process_p ARGUMENT
270 # Process the -p flag.
273 cmdline2
="${cmdline2}p '$1'"
276 # function: process_R ARGUMENT
278 # Process the -R flag.
280 local local_name
=`include_file_or_directory runtime $1`
281 test "X$local_name" != "X" ||
return
282 cmdline2
="${cmdline2}R 'runtime/$local_name'"
285 # function: include_file_or_directory PREFIX NAME
287 # Include the given file or directory in the client's temporary
288 # tree to be sent to the server.
289 function include_file_or_directory
{
290 # Add a symbolic link of the named file or directory to our temporary directory
291 local local_name
=`generate_client_temp_name $2`
292 mkdir
-p $tmpdir_client/$1/`dirname $local_name` || \
293 fatal
"ERROR: could not create $tmpdir_client/$1/`dirname $local_name`"
294 ln -s /$local_name $tmpdir_client/$1/$local_name || \
295 fatal
"ERROR: could not link $tmpdir_client/$1/$local_name to /$local_name"
299 # function: generate_client_temp_name NAME
301 # Generate the name to be used for the given file/directory relative to the
302 # client's temporary directory.
303 function generate_client_temp_name
{
304 # Transform the name into a fully qualified path name
305 local full_name
=`echo "$1" | sed "s,^\\\([^/]\\\),$wd/\\\\1,"`
307 # The same name without the initial / or trailing /
308 local local_name
=`echo "$full_name" | sed 's,^/\(.*\),\1,'`
309 local_name
=`echo "$local_name" | sed 's,\(.*\)/$,\1,'`
313 # function: create_request
315 # Add information to the client's temp directory representing the request
317 function create_request
{
318 # Work in our temporary directory
321 if test "X$script_file" != "X"; then
322 if test "$script_file" = "-"; then
323 mkdir
-p $tmpdir_client/script || \
324 fatal
"ERROR: cannot create temporary directory " $tmpdir_client/script
325 cat > $tmpdir_client/script
/$script_file
327 include_file_or_directory
script $script_file > /dev
/null
331 # Add the necessary info to special files in our temporary directory.
332 echo "cmdline: $cmdline" > cmdline
333 echo "sysinfo: `client_sysinfo`" > sysinfo
336 # function client_sysinfo
338 # Generate the client's sysinfo and echo it to stdout
339 function client_sysinfo
{
340 if test "X$sysinfo_client" = "X"; then
341 # Add some info from uname
342 sysinfo_client
="`uname -rvm`"
344 echo "$sysinfo_client"
347 # function: package_request
349 # Package the client's temp directory into a form suitable for sending to the
351 function package_request
{
352 # Package up the temporary directory into a tar file
355 local tmpdir_client_base
=`basename $tmpdir_client`
356 tar_client
=$tmpdir_env/`mktemp $tmpdir_client_base.tgz.XXXXXX` || \
357 fatal
"ERROR: cannot create temporary file " $tar_client
359 tar -czhf $tar_client $tmpdir_client_base || \
360 fatal
"ERROR: tar of request tree, $tmpdir_client, failed"
363 # function: send_request
365 # Notify the server and then send $tar_client to the server
367 # client -> "request:"
369 # client -> $tar_client
370 function send_request
{
372 # Get the server's response.
375 check_server_error
$line
377 # Check for the proper response.
378 test "$line" = "ready:" || \
379 fatal
"ERROR: server response, '$line', is incorrect"
381 # Send the request file. We need to redirect to /dev/null
382 # in order to workaround a nc bug. It closes the connection
383 # early if stdin from the other side is not provided.
384 until nc
$server $
(($port + 1)) < $tar_client > /dev
/null
390 # function: receive_response
392 # Wait for a response from the server indicating the results of our request.
394 # server -> "{done,failed}:"
395 # server -> $tar_server
396 function receive_response
{
397 # Get the server's response.
400 check_server_error
$line
402 # Check for the proper response.
403 if test "$line" != "done:"; then
404 if test "$line" != "failed:"; then
405 fatal
"ERROR: server response, '$line', is incorrect"
410 # Make a place to receive the response file.
411 tar_server
=`mktemp -t $tmpdir_prefix_client.server.tgz.XXXXXX` || \
412 fatal
"ERROR: cannot create temporary file " $tar_server
414 # Retrieve the file. We need to redirect stdin from /dev/zero to work
415 # around a bug in nc. It closes the connection early is stdin is not
417 until nc
$server $
(($port + 1)) < /dev
/zero
> $tar_server
423 # function: unpack_response
425 # Unpack the tar file received from the server and make the contents available
426 # for printing the results and/or running 'staprun'.
427 function unpack_response
{
428 tmpdir_server
=`mktemp -dt $tmpdir_prefix_client.server.XXXXXX` || \
429 fatal
"ERROR: cannot create temporary file " $tmpdir_server
431 # Unpack the server output directory
433 tar -xzf $tar_server || \
434 fatal
"ERROR: Unpacking of server response, $tar_server, failed"
436 # Identify the server's response tree. The tar file should have expanded
437 # into a single directory named to match $tmpdir_prefix_server.??????
438 # which should now be the only item in the current directory.
439 test "`ls | wc -l`" = 1 || \
440 fatal
"ERROR: Wrong number of files after expansion of server's tar file"
443 tmpdir_server
=`expr "$tmpdir_server" : "\\\($tmpdir_prefix_server\\\\.......\\\)"`
445 test "X$tmpdir_server" != "X" || \
446 fatal
"ERROR: server tar file did not expand as expected"
448 # Check the contents of the expanded directory. It should contain:
449 # 1) a file called stdout
450 # 2) a file called stderr
451 # 3) optionally a directory named to match stap??????
452 local num_files
=`ls $tmpdir_server | wc -l`
453 test $num_files = 3 -o $num_files = 2 || \
454 fatal
"ERROR: Wrong number of files in server's temp directory"
455 test -f $tmpdir_server/stdout || \
456 fatal
"ERROR: `pwd`/$tmpdir_server/stdout does not exist or is not a regular file"
457 test -f $tmpdir_server/stderr || \
458 fatal
"ERROR: `pwd`/$tmpdir_server/stderr does not exist or is not a regular file"
460 # See if there is a systemtap temp directory
461 tmpdir_stap
=`ls $tmpdir_server | grep stap`
462 tmpdir_stap
=`expr "$tmpdir_stap" : "\\\(stap......\\\)"`
463 if test "X$tmpdir_stap" != "X"; then
464 test -d $tmpdir_server/$tmpdir_stap || \
465 fatal
"ERROR: `pwd`/$tmpdir_server/$tmpdir_stap is not a directory"
467 # Move the systemtap temp directory to a local temp location, if -k
469 if test $keep_temps = 1; then
470 local local_tmpdir_stap
=`mktemp -dt stapXXXXXX` || \
471 fatal
"ERROR: cannot create temporary directory " $local_tmpdir_stap
472 mv $tmpdir_server/$tmpdir_stap/* $local_tmpdir_stap 2>/dev
/null
473 rm -fr $tmpdir_server/$tmpdir_stap
475 # Correct the name of the temp directory in the server's stderr output
476 sed -i "s,^Keeping temporary directory.*,Keeping temporary directory \"$local_tmpdir_stap\"," $tmpdir_server/stderr
477 tmpdir_stap
=$local_tmpdir_stap
479 # Make sure we own the systemtap temp directory if we are root.
480 test $EUID = 0 && chown
$EUID:$EUID $tmpdir_server/$tmpdir_stap
481 # The temp directory will be moved to here below.
482 tmpdir_stap
=`pwd`/$tmpdir_stap
486 # Move the contents of the server's tmpdir down one level to the
487 # current directory (our local server tmpdir)
488 mv $tmpdir_server/* .
2>/dev
/null
489 rm -fr $tmpdir_server
493 # function: find_and_connect_to_server
495 # Find and establish connection with a compatible stap server.
496 function find_and_connect_to_server
{
497 # Use a temp file here instead of a pipeline so that the side effects
498 # of choose_server are seen by the rest of this script.
500 stap-find-servers
> servers
501 choose_server
< servers
505 # function: choose_server
507 # Examine each line from stdin and attempt to connect to each server
508 # specified until successful.
509 function choose_server
{
512 while read name server port remain
514 num_servers
=$
(($num_servers + 1))
516 if test "X$server" = "X"; then
517 fatal
"ERROR: server ip address not provided"
520 if test "X$port" = "X"; then
521 fatal
"ERROR: server port not provided"
524 if connect_to_server
$server $port; then
529 if test num_servers
= 0; then
530 fatal
"ERROR: cannot find a server"
533 fatal
"ERROR: unable to connect to a server"
536 # function: connect_to_server IP PORT
538 # Establish connection with the given server
539 function connect_to_server
{
540 # Open a connection to the server
541 exec 3<> /dev
/tcp
/$1/$2
544 # function: disconnect_from_server
546 # Disconnect from the server.
547 function disconnect_from_server
{
548 # Close the connection to the server.
552 # function: stream_output
554 # Write the stdout and stderr from the server to stdout and stderr respectively.
555 function stream_output
{
556 # Output stdout and stderr as directed
559 eval cat stdout
$stdout_redirection
562 # function: maybe_call_staprun
564 # Call staprun using the module returned from the server, if requested.
565 function maybe_call_staprun
{
566 if test $rc != 0; then
567 # stap run on the server failed, so don't bother
571 if test $p_phase -ge 4; then
572 # There should be a systemtap temporary directory.
573 if test "X$tmpdir_stap" = "X"; then
574 # OK if no script specified
575 if test "X$e_script" != "X" -o "X$script_file" != "X"; then
576 fatal
"ERROR: systemtap temporary directory is missing in server response"
581 # There should be a module.
582 local mod_name
=`ls $tmpdir_stap | grep '.ko$'`
583 if test "X$mod_name" = "X"; then
584 fatal
"ERROR: no module was found in $tmpdir_stap"
587 if test $p_phase = 5; then
588 # We have a module. Try to run it
589 # If a -c command was specified, pass it along.
590 if test "X$c_cmd" != "X"; then
591 staprun_opts
="-c '$c_cmd'"
594 # The -v level will be one less than what was specified
596 for ((--v_level; $v_level > 0; --v_level))
598 staprun_opts
="$staprun_opts -v"
601 # Run it in the background and wait for it. This
602 # way any signals send to us can be caught.
603 PATH
=`staprun_PATH` eval staprun
"$staprun_opts" \
604 $tmpdir_stap/`ls $tmpdir_stap | grep '.ko$'` &
607 # 127 from wait means that the job was already finished.
613 # function: staprun_PATH
615 # Compute a PATH suitable for running staprun.
616 function staprun_PATH
{
617 # staprun may invoke 'stap'. So we can use the current PATH if we were
618 # not invoked as 'stap' or we are not the first 'stap' on the PATH.
619 local first_stap
=`which stap 2>/dev/null`
620 if test `which $0 2>/dev/null` != $first_stap; then
625 # Otherwise, remove the PATH component where we live from the PATH
626 local PATH_component
=`dirname $first_stap`
627 echo "$PATH" |
sed "s,$PATH_component,,g"
630 # function: check_server_error SERVER_RESPONSE
632 # Check the given server response for an error message.
633 function check_server_error
{
634 echo "$1" |
grep -q "^ERROR:" && \
635 server_fatal
"Server:" "$@"
638 # function: fatal [ MESSAGE ]
641 # Prints its arguments to stderr and exits
644 disconnect_from_server
649 # function: server_fatal [ MESSAGE ]
652 # Prints its arguments to stderr and exits
653 function server_fatal
{
656 disconnect_from_server
663 # Cleanup work files unless asked to keep them.
667 if test $keep_temps != 1; then
668 rm -fr $tmpdir_client
671 rm -fr $tmpdir_server
675 # function: terminate
677 # Terminate gracefully.
680 echo "$0: terminated by signal"
683 # Kill any running staprun job
684 kill -s SIGTERM
%?staprun
689 # function: interrupt
691 # Pass an interrupt (ctrl-C) to staprun
693 # Pass the signal on to any running staprun job
694 kill -s SIGINT
%?staprun
697 #-----------------------------------------------------------------------------
698 # Beginning of main line execution.
699 #-----------------------------------------------------------------------------
705 find_and_connect_to_server
708 disconnect_from_server