]> sourceware.org Git - systemtap.git/blob - stap-client
Merge branch 'master' of ssh://sources.redhat.com/git/systemtap
[systemtap.git] / stap-client
1 #!/bin/bash
2
3 # Compile server client for systemtap
4 #
5 # Copyright (C) 2008 Red Hat Inc.
6 #
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
10 # later version.
11
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.
18
19 # Catch ctrl-c and other termination signals
20 trap 'terminate' SIGTERM
21 trap 'interrupt' SIGINT
22
23 #-----------------------------------------------------------------------------
24 # Helper functions.
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
31 }
32
33 # function: initialization
34 function initialization {
35 rc=0
36 wd=`pwd`
37 umask 0
38
39 # Default options settings
40 p_phase=5
41 v_level=0
42 keep_temps=0
43
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`
50 }
51
52 # function: parse_options [ STAP-OPTIONS ]
53 #
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
57 # output.
58 #
59 function parse_options {
60 cmdline=
61 cmdline1=
62 cmdline2=
63 while test $# != 0
64 do
65 advance_p=0
66 dash_seen=0
67
68 # Start of a new token.
69 first_token=$1
70 until test $advance_p != 0
71 do
72 # Identify the next option
73 first_char=`expr "$first_token" : '\(.\).*'`
74 second_char=
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.
83 # Remove the dash.
84 first_token=`expr "$first_token" : '-\(.*\)'`
85 dash_seen=1
86 first_char=`expr "$first_token" : '\(.\).*'`
87 cmdline2="$cmdline2 -"
88 fi
89 fi
90 fi
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
100 cmdline1="$cmdline2"
101 cmdline2=
102 else
103 cmdline2="$cmdline2 '$first_token'"
104 fi
105 advance_p=$(($advance_p + 1))
106 break
107 fi
108 fi
109
110 # We are at the start of an option. Look at the first character.
111 case $first_char in
112 c)
113 get_arg $first_token "$2"
114 process_c "$stap_arg"
115 ;;
116 D)
117 get_arg $first_token $2
118 cmdline2="${cmdline2}D '$stap_arg'"
119 ;;
120 e)
121 get_arg $first_token "$2"
122 process_e "$stap_arg"
123 ;;
124 I)
125 get_arg $first_token $2
126 process_I $stap_arg
127 ;;
128 k)
129 keep_temps=1
130 ;;
131 l)
132 get_arg $first_token $2
133 cmdline2="${cmdline2}l '$stap_arg'"
134 ;;
135 m)
136 get_arg $first_token $2
137 cmdline2="${cmdline2}m $stap_arg"
138 ;;
139 o)
140 get_arg $first_token $2
141 process_o $stap_arg
142 ;;
143 p)
144 get_arg $first_token $2
145 process_p $stap_arg
146 ;;
147 r)
148 get_arg $first_token $2
149 cmdline2="${cmdline2}r $stap_arg"
150 ;;
151 R)
152 get_arg $first_token $2
153 process_R $stap_arg
154 ;;
155 s)
156 get_arg $first_token $2
157 cmdline2="${cmdline2}s $stap_arg"
158 ;;
159 v)
160 v_level=$(($v_level + 1))
161 ;;
162 x)
163 get_arg $first_token $2
164 cmdline2="${cmdline2}x $stap_arg"
165 ;;
166 *)
167 # An unknown or unimportant flag. Ignore it, but pass it on to the server.
168 ;;
169 esac
170
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))
177 fi
178 fi
179 done
180
181 # Consume the arguments we just processed.
182 while test $advance_p != 0
183 do
184 shift
185 advance_p=$(($advance_p - 1))
186 done
187 done
188
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
192 local local_name
193 if test "$script_file" != "-"; then
194 local_name=`generate_client_temp_name $script_file`
195 else
196 local_name=$script_file
197 fi
198 cmdline="$cmdline1 script/$local_name $cmdline2"
199 else
200 cmdline="$cmdline1 $cmdline2"
201 fi
202 }
203
204 # function: get_arg FIRSTWORD SECONDWORD
205 #
206 # Collect an argument to the given option
207 function get_arg {
208 # Remove first character.
209 local opt=`expr "$1" : '\(.\).*'`
210 local first=`expr "$1" : '.\(.*\)'`
211
212 # Advance to the next token, if the first one is exhausted.
213 if test "X$first" = "X"; then
214 shift
215 advance_p=$(($advance_p + 1))
216 first=$1
217 fi
218
219 test "X$first" != "X" || \
220 fatal "Missing argument to -$opt"
221
222 stap_arg="$first"
223 advance_p=$(($advance_p + 1))
224 }
225
226 # function: process_c ARGUMENT
227 #
228 # Process the -c flag.
229 function process_c {
230 c_cmd="$1"
231 cmdline2="${cmdline2}c '$1'"
232 }
233
234 # function: process_e ARGUMENT
235 #
236 # Process the -e flag.
237 function process_e {
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
241 e_script="$1"
242 if test "X$script_file" != "X"; then
243 cmdline1="$cmdline1 $script_file $cmdline2"
244 cmdline2=
245 script_file=
246 fi
247 fi
248 cmdline2="${cmdline2}e '$1'"
249 }
250
251 # function: process_I ARGUMENT
252 #
253 # Process the -I flag.
254 function process_I {
255 local local_name=`include_file_or_directory tapsets $1`
256 test "X$local_name" != "X" || return
257 cmdline2="${cmdline2}I 'tapsets/$local_name'"
258 }
259
260 # function: process_o ARGUMENT
261 #
262 # Process the -o flag.
263 function process_o {
264 stdout_redirection="> $1"
265 cmdline2="${cmdline2}o '$1'"
266 }
267
268 # function: process_p ARGUMENT
269 #
270 # Process the -p flag.
271 function process_p {
272 p_phase=$1
273 cmdline2="${cmdline2}p '$1'"
274 }
275
276 # function: process_R ARGUMENT
277 #
278 # Process the -R flag.
279 function process_R {
280 local local_name=`include_file_or_directory runtime $1`
281 test "X$local_name" != "X" || return
282 cmdline2="${cmdline2}R 'runtime/$local_name'"
283 }
284
285 # function: include_file_or_directory PREFIX NAME
286 #
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"
296 echo "$local_name"
297 }
298
299 # function: generate_client_temp_name NAME
300 #
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,"`
306
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,'`
310 echo "$local_name"
311 }
312
313 # function: create_request
314 #
315 # Add information to the client's temp directory representing the request
316 # to the server.
317 function create_request {
318 # Work in our temporary directory
319 cd $tmpdir_client
320
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
326 else
327 include_file_or_directory script $script_file > /dev/null
328 fi
329 fi
330
331 # Add the necessary info to special files in our temporary directory.
332 echo "cmdline: $cmdline" > cmdline
333 echo "sysinfo: `client_sysinfo`" > sysinfo
334 }
335
336 # function client_sysinfo
337 #
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`"
343 fi
344 echo "$sysinfo_client"
345 }
346
347 # function: package_request
348 #
349 # Package the client's temp directory into a form suitable for sending to the
350 # server.
351 function package_request {
352 # Package up the temporary directory into a tar file
353 cd $tmpdir_env
354
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
358
359 tar -czhf $tar_client $tmpdir_client_base || \
360 fatal "ERROR: tar of request tree, $tmpdir_client, failed"
361 }
362
363 # function: send_request
364 #
365 # Notify the server and then send $tar_client to the server
366 # The protocol is:
367 # client -> "request:"
368 # server -> "ready:"
369 # client -> $tar_client
370 function send_request {
371 echo "request:" >&3
372 # Get the server's response.
373 read <&3
374 local line=$REPLY
375 check_server_error $line
376
377 # Check for the proper response.
378 test "$line" = "ready:" || \
379 fatal "ERROR: server response, '$line', is incorrect"
380
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
385 do
386 sleep 1
387 done
388 }
389
390 # function: receive_response
391 #
392 # Wait for a response from the server indicating the results of our request.
393 # protocol is:
394 # server -> "{done,failed}:"
395 # server -> $tar_server
396 function receive_response {
397 # Get the server's response.
398 read <&3
399 local line=$REPLY
400 check_server_error $line
401
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"
406 fi
407 rc=1
408 fi
409
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
413
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
416 # provided.
417 until nc $server $(($port + 1)) < /dev/zero > $tar_server
418 do
419 sleep 1
420 done
421 }
422
423 # function: unpack_response
424 #
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
430
431 # Unpack the server output directory
432 cd $tmpdir_server
433 tar -xzf $tar_server || \
434 fatal "ERROR: Unpacking of server response, $tar_server, failed"
435
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"
441
442 tmpdir_server=`ls`
443 tmpdir_server=`expr "$tmpdir_server" : "\\\($tmpdir_prefix_server\\\\.......\\\)"`
444
445 test "X$tmpdir_server" != "X" || \
446 fatal "ERROR: server tar file did not expand as expected"
447
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"
459
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"
466
467 # Move the systemtap temp directory to a local temp location, if -k
468 # was specified.
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
474
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
478 else
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
483 fi
484 fi
485
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
490 tmpdir_server=`pwd`
491 }
492
493 # function: find_and_connect_to_server
494 #
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.
499 cd $tmpdir_client
500 stap-find-servers > servers
501 choose_server < servers
502 rm -fr servers
503 }
504
505 # function: choose_server
506 #
507 # Examine each line from stdin and attempt to connect to each server
508 # specified until successful.
509 function choose_server {
510 local num_servers=0
511 local name
512 while read name server port remain
513 do
514 num_servers=$(($num_servers + 1))
515
516 if test "X$server" = "X"; then
517 fatal "ERROR: server ip address not provided"
518 fi
519
520 if test "X$port" = "X"; then
521 fatal "ERROR: server port not provided"
522 fi
523
524 if connect_to_server $server $port; then
525 return 0
526 fi
527 done
528
529 if test num_servers = 0; then
530 fatal "ERROR: cannot find a server"
531 fi
532
533 fatal "ERROR: unable to connect to a server"
534 }
535
536 # function: connect_to_server IP PORT
537 #
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
542 }
543
544 # function: disconnect_from_server
545 #
546 # Disconnect from the server.
547 function disconnect_from_server {
548 # Close the connection to the server.
549 exec 3<&-
550 }
551
552 # function: stream_output
553 #
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
557 cd $tmpdir_server
558 cat stderr >&2
559 eval cat stdout $stdout_redirection
560 }
561
562 # function: maybe_call_staprun
563 #
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
568 return
569 fi
570
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"
577 fi
578 return
579 fi
580
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"
585 fi
586
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'"
592 fi
593
594 # The -v level will be one less than what was specified
595 # for us.
596 for ((--v_level; $v_level > 0; --v_level))
597 do
598 staprun_opts="$staprun_opts -v"
599 done
600
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$'` &
605 wait %?staprun
606 rc=$?
607 # 127 from wait means that the job was already finished.
608 test $rc=127 && rc=0
609 fi
610 fi
611 }
612
613 # function: staprun_PATH
614 #
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
621 echo "$PATH"
622 return
623 fi
624
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"
628 }
629
630 # function: check_server_error SERVER_RESPONSE
631 #
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:" "$@"
636 }
637
638 # function: fatal [ MESSAGE ]
639 #
640 # Fatal error
641 # Prints its arguments to stderr and exits
642 function fatal {
643 echo "$0:" "$@" >&2
644 disconnect_from_server
645 cleanup
646 exit 1
647 }
648
649 # function: server_fatal [ MESSAGE ]
650 #
651 # Fatal error
652 # Prints its arguments to stderr and exits
653 function server_fatal {
654 echo "$0:" "$@" >&2
655 cat <&3 >&2
656 disconnect_from_server
657 cleanup
658 exit 1
659 }
660
661 # function cleanup
662 #
663 # Cleanup work files unless asked to keep them.
664 function cleanup {
665 # Clean up.
666 cd $tmpdir_env
667 if test $keep_temps != 1; then
668 rm -fr $tmpdir_client
669 rm -f $tar_client
670 rm -f $tar_server
671 rm -fr $tmpdir_server
672 fi
673 }
674
675 # function: terminate
676 #
677 # Terminate gracefully.
678 function terminate {
679 # Clean up
680 echo "$0: terminated by signal"
681 cleanup
682
683 # Kill any running staprun job
684 kill -s SIGTERM %?staprun
685
686 exit 1
687 }
688
689 # function: interrupt
690 #
691 # Pass an interrupt (ctrl-C) to staprun
692 function interrupt {
693 # Pass the signal on to any running staprun job
694 kill -s SIGINT %?staprun
695 }
696
697 #-----------------------------------------------------------------------------
698 # Beginning of main line execution.
699 #-----------------------------------------------------------------------------
700 configuration
701 initialization
702 parse_options "$@"
703 create_request
704 package_request
705 find_and_connect_to_server
706 send_request
707 receive_response
708 disconnect_from_server
709 unpack_response
710 stream_output
711 maybe_call_staprun
712 cleanup
713
714 exit $rc
This page took 0.072295 seconds and 6 git commands to generate.