]> sourceware.org Git - systemtap.git/blob - stap-client
Merge commit 'origin/master' into pr4225
[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 trap 'ignore_signal' SIGHUP SIGPIPE
23
24 #-----------------------------------------------------------------------------
25 # Helper functions.
26 #-----------------------------------------------------------------------------
27 # function: configuration
28 function configuration {
29 tmpdir_prefix_client=stap.client
30 tmpdir_prefix_server=stap.server
31 avahi_service_tag=_stap._tcp
32 }
33
34 # function: initialization
35 function initialization {
36 rc=0
37 wd=`pwd`
38 umask 0
39 staprun_running=0
40
41 # Default options settings
42 p_phase=5
43 v_level=0
44 keep_temps=0
45 b_specified=0
46
47 # Create a temporary directory to package things in
48 # Do this before parsing the command line so that there is a place
49 # to put -I and -R directories.
50 tmpdir_client=`mktemp -dt $tmpdir_prefix_client.XXXXXX` || \
51 fatal "ERROR: cannot create temporary directory " $tmpdir_client
52 tmpdir_env=`dirname $tmpdir_client`
53 }
54
55 # function: parse_options [ STAP-OPTIONS ]
56 #
57 # Examine the command line. We need not do much checking, but we do need to
58 # parse all options in order to discover the ones we're interested in.
59 # The server will take care of most situations and return the appropriate
60 # output.
61 #
62 function parse_options {
63 cmdline=
64 cmdline1=
65 cmdline2=
66 while test $# != 0
67 do
68 advance_p=0
69 dash_seen=0
70
71 # Start of a new token.
72 first_token=$1
73 until test $advance_p != 0
74 do
75 # Identify the next option
76 first_char=`expr "$first_token" : '\(.\).*'`
77 second_char=
78 if test $dash_seen = 0; then
79 if test "$first_char" = "-"; then
80 if test "$first_token" != "-"; then
81 # It's not a lone dash, so it's an option.
82 # Is it a long option (i.e. --option)?
83 second_char=`expr "$first_token" : '.\(.\).*'`
84 if test "$second_char" != "-"; then
85 # It's not a lone dash, or a long option, so it's a short option string.
86 # Remove the dash.
87 first_token=`expr "$first_token" : '-\(.*\)'`
88 dash_seen=1
89 first_char=`expr "$first_token" : '\(.\).*'`
90 cmdline2="$cmdline2 -"
91 fi
92 fi
93 fi
94 if test $dash_seen = 0; then
95 # The dash has not been seen. This is either the script file
96 # name, a long argument or an argument to be passed to the probe module.
97 # If this is the first time, and -e has not been specified,
98 # then it could be the name of the script file.
99 if test "X$second_char" = "X-"; then
100 cmdline2="$cmdline2 $first_token"
101 elif test "X$e_script" = "X" -a "X$script_file" = "X"; then
102 script_file=$first_token
103 cmdline1="$cmdline2"
104 cmdline2=
105 elif test "$first_char" != "'"; then
106 cmdline2="$cmdline2 '$first_token'"
107 else
108 cmdline2="$cmdline2 $first_token"
109 fi
110 advance_p=$(($advance_p + 1))
111 break
112 fi
113 fi
114
115 # We are at the start of an option. Look at the first character.
116 case $first_char in
117 b)
118 b_specified=1
119 ;;
120 c)
121 get_arg $first_token "$2"
122 process_c "$stap_arg"
123 ;;
124 D)
125 get_arg $first_token $2
126 cmdline2="${cmdline2}D '$stap_arg'"
127 ;;
128 e)
129 get_arg $first_token "$2"
130 process_e "$stap_arg"
131 ;;
132 I)
133 get_arg $first_token $2
134 process_I $stap_arg
135 ;;
136 k)
137 keep_temps=1
138 ;;
139 l)
140 get_arg $first_token $2
141 cmdline2="${cmdline2}l '$stap_arg'"
142 ;;
143 m)
144 get_arg $first_token $2
145 process_m $stap_arg
146 ;;
147 o)
148 get_arg $first_token $2
149 process_o $stap_arg
150 ;;
151 p)
152 get_arg $first_token $2
153 process_p $stap_arg
154 ;;
155 r)
156 get_arg $first_token $2
157 cmdline2="${cmdline2}r '$stap_arg'"
158 ;;
159 R)
160 get_arg $first_token $2
161 process_R $stap_arg
162 ;;
163 s)
164 get_arg $first_token $2
165 cmdline2="${cmdline2}s '$stap_arg'"
166 ;;
167 v)
168 v_level=$(($v_level + 1))
169 ;;
170 x)
171 get_arg $first_token $2
172 cmdline2="${cmdline2}x '$stap_arg'"
173 ;;
174 *)
175 # An unknown or unimportant flag. Ignore it, but pass it on to the server.
176 ;;
177 esac
178
179 if test $advance_p = 0; then
180 # Just another flag character. Consume it.
181 cmdline2="$cmdline2$first_char"
182 first_token=`expr "$first_token" : '.\(.*\)'`
183 if test "X$first_token" = "X"; then
184 advance_p=$(($advance_p + 1))
185 fi
186 fi
187 done
188
189 # Consume the arguments we just processed.
190 while test $advance_p != 0
191 do
192 shift
193 advance_p=$(($advance_p - 1))
194 done
195 done
196
197 # If the script file was given and it's not '-', then replace it with its
198 # client-temp-name in the command string.
199 if test "X$script_file" != "X"; then
200 local local_name
201 if test "$script_file" != "-"; then
202 local_name=`generate_client_temp_name $script_file`
203 else
204 local_name=$script_file
205 fi
206 cmdline="$cmdline1 script/$local_name $cmdline2"
207 else
208 cmdline="$cmdline1 $cmdline2"
209 fi
210 }
211
212 # function: get_arg FIRSTWORD SECONDWORD
213 #
214 # Collect an argument to the given option
215 function get_arg {
216 # Remove first character.
217 local opt=`expr "$1" : '\(.\).*'`
218 local first=`expr "$1" : '.\(.*\)'`
219
220 # Advance to the next token, if the first one is exhausted.
221 if test "X$first" = "X"; then
222 shift
223 advance_p=$(($advance_p + 1))
224 first=$1
225 fi
226
227 test "X$first" != "X" || \
228 fatal "Missing argument to -$opt"
229
230 stap_arg="$first"
231 advance_p=$(($advance_p + 1))
232 }
233
234 # function: process_c ARGUMENT
235 #
236 # Process the -c flag.
237 function process_c {
238 c_cmd="$1"
239 cmdline2="${cmdline2}c '$1'"
240 }
241
242 # function: process_e ARGUMENT
243 #
244 # Process the -e flag.
245 function process_e {
246 # Only the first -e option is recognized and it overrides any script file name
247 # which may have already been identified.
248 if test "X$e_script" = "X"; then
249 e_script="$1"
250 if test "X$script_file" != "X"; then
251 cmdline1="$cmdline1 $script_file $cmdline2"
252 cmdline2=
253 script_file=
254 fi
255 fi
256 cmdline2="${cmdline2}e '$1'"
257 }
258
259 # function: process_I ARGUMENT
260 #
261 # Process the -I flag.
262 function process_I {
263 local local_name=`include_file_or_directory tapsets $1`
264 test "X$local_name" != "X" || return
265 cmdline2="${cmdline2}I 'tapsets/$local_name'"
266 }
267
268 # function: process_m ARGUMENT
269 #
270 # Process the -m flag.
271 function process_m {
272 m_name="$1"
273 cmdline2="${cmdline2}m '$1'"
274 }
275
276 # function: process_o ARGUMENT
277 #
278 # Process the -o flag.
279 function process_o {
280 stdout_redirection="$1"
281 cmdline2="${cmdline2}o '$1'"
282 }
283
284 # function: process_p ARGUMENT
285 #
286 # Process the -p flag.
287 function process_p {
288 p_phase=$1
289 cmdline2="${cmdline2}p '$1'"
290 }
291
292 # function: process_R ARGUMENT
293 #
294 # Process the -R flag.
295 function process_R {
296 local local_name=`include_file_or_directory runtime $1`
297 test "X$local_name" != "X" || return
298 cmdline2="${cmdline2}R 'runtime/$local_name'"
299 }
300
301 # function: include_file_or_directory PREFIX NAME
302 #
303 # Include the given file or directory in the client's temporary
304 # tree to be sent to the server.
305 function include_file_or_directory {
306 # Add a symbolic link of the named file or directory to our temporary directory
307 local local_name=`generate_client_temp_name $2`
308 mkdir -p $tmpdir_client/$1/`dirname $local_name` || \
309 fatal "ERROR: could not create $tmpdir_client/$1/`dirname $local_name`"
310 ln -s /$local_name $tmpdir_client/$1/$local_name || \
311 fatal "ERROR: could not link $tmpdir_client/$1/$local_name to /$local_name"
312 echo "$local_name"
313 }
314
315 # function: generate_client_temp_name NAME
316 #
317 # Generate the name to be used for the given file/directory relative to the
318 # client's temporary directory.
319 function generate_client_temp_name {
320 # Transform the name into a fully qualified path name
321 local full_name=`echo "$1" | sed "s,^\\\([^/]\\\),$wd/\\\\1,"`
322
323 # The same name without the initial / or trailing /
324 local local_name=`echo "$full_name" | sed 's,^/\(.*\),\1,'`
325 local_name=`echo "$local_name" | sed 's,\(.*\)/$,\1,'`
326 echo "$local_name"
327 }
328
329 # function: create_request
330 #
331 # Add information to the client's temp directory representing the request
332 # to the server.
333 function create_request {
334 # Work in our temporary directory
335 cd $tmpdir_client
336
337 if test "X$script_file" != "X"; then
338 if test "$script_file" = "-"; then
339 mkdir -p $tmpdir_client/script || \
340 fatal "ERROR: cannot create temporary directory " $tmpdir_client/script
341 cat > $tmpdir_client/script/$script_file
342 else
343 include_file_or_directory script $script_file > /dev/null
344 fi
345 fi
346
347 # Add the necessary info to special files in our temporary directory.
348 echo "cmdline: $cmdline" > cmdline
349 echo "sysinfo: `client_sysinfo`" > sysinfo
350 }
351
352 # function client_sysinfo
353 #
354 # Generate the client's sysinfo and echo it to stdout
355 function client_sysinfo {
356 if test "X$sysinfo_client" = "X"; then
357 # Add some info from uname
358 sysinfo_client="`uname -rvm`"
359 fi
360 echo "$sysinfo_client"
361 }
362
363 # function: package_request
364 #
365 # Package the client's temp directory into a form suitable for sending to the
366 # server.
367 function package_request {
368 # Package up the temporary directory into a tar file
369 cd $tmpdir_env
370
371 local tmpdir_client_base=`basename $tmpdir_client`
372 tar_client=$tmpdir_env/`mktemp $tmpdir_client_base.tgz.XXXXXX` || \
373 fatal "ERROR: cannot create temporary file " $tar_client
374
375 tar -czhf $tar_client $tmpdir_client_base || \
376 fatal "ERROR: tar of request tree, $tmpdir_client, failed"
377 }
378
379 # function: send_request
380 #
381 # Notify the server and then send $tar_client to the server
382 # The protocol is:
383 # client -> "request:"
384 # client -> $tar_client
385 function send_request {
386 # Send the request file.
387 for ((attempt=0; $attempt < 10; ++attempt))
388 do
389 if nc -w10 $server $(($port+1)) < $tar_client > /dev/null 2>&1; then
390 return;
391 fi
392 sleep 1
393 done
394 fatal "ERROR: Unable to connect to server while sending request file"
395 }
396
397 # function: receive_response
398 #
399 # Wait for a response from the server indicating the results of our request.
400 function receive_response {
401 # Make a place to receive the response file.
402 tar_server=`mktemp -t $tmpdir_prefix_client.server.tgz.XXXXXX` || \
403 fatal "ERROR: cannot create temporary file " $tar_server
404
405 # Retrieve the file. Wait for up to 5 minutes for a response.
406 for ((attempt=0; $attempt < 300; ++attempt))
407 do
408 if nc -d $server $(($port+1)) > $tar_server 2>/dev/null; then
409 return;
410 fi
411 sleep 1
412 done
413 fatal "ERROR: Unable to connect to server while receiving response file"
414 }
415
416 # function: unpack_response
417 #
418 # Unpack the tar file received from the server and make the contents available
419 # for printing the results and/or running 'staprun'.
420 function unpack_response {
421 tmpdir_server=`mktemp -dt $tmpdir_prefix_client.server.XXXXXX` || \
422 fatal "ERROR: cannot create temporary file " $tmpdir_server
423
424 # Unpack the server output directory
425 cd $tmpdir_server
426 tar -xzf $tar_server || \
427 fatal "ERROR: Unpacking of server response, $tar_server, failed"
428
429 # Identify the server's response tree. The tar file should have expanded
430 # into a single directory named to match $tmpdir_prefix_server.??????
431 # which should now be the only item in the current directory.
432 test "`ls | wc -l`" = 1 || \
433 fatal "ERROR: Wrong number of files after expansion of server's tar file"
434
435 tmpdir_server=`ls`
436 tmpdir_server=`expr "$tmpdir_server" : "\\\($tmpdir_prefix_server\\\\.......\\\)"`
437
438 test "X$tmpdir_server" != "X" || \
439 fatal "ERROR: server tar file did not expand as expected"
440
441 # Check the contents of the expanded directory. It should contain:
442 # 1) a file called stdout
443 # 2) a file called stderr
444 # 3) a file called rc
445 # 4) optionally a directory named to match stap??????
446 local num_files=`ls $tmpdir_server | wc -l`
447 test $num_files = 4 -o $num_files = 3 || \
448 fatal "ERROR: Wrong number of files in server's temp directory"
449 test -f $tmpdir_server/stdout || \
450 fatal "ERROR: `pwd`/$tmpdir_server/stdout does not exist or is not a regular file"
451 test -f $tmpdir_server/stderr || \
452 fatal "ERROR: `pwd`/$tmpdir_server/stderr does not exist or is not a regular file"
453 test -f $tmpdir_server/rc || \
454 fatal "ERROR: `pwd`/$tmpdir_server/rc does not exist or is not a regular file"
455
456 # See if there is a systemtap temp directory
457 tmpdir_stap=`ls $tmpdir_server | grep stap`
458 tmpdir_stap=`expr "$tmpdir_stap" : "\\\(stap......\\\)"`
459 if test "X$tmpdir_stap" != "X"; then
460 test -d $tmpdir_server/$tmpdir_stap || \
461 fatal "ERROR: `pwd`/$tmpdir_server/$tmpdir_stap is not a directory"
462
463 # Move the systemtap temp directory to a local temp location, if -k
464 # was specified.
465 if test $keep_temps = 1; then
466 local local_tmpdir_stap=`mktemp -dt stapXXXXXX` || \
467 fatal "ERROR: cannot create temporary directory " $local_tmpdir_stap
468 mv $tmpdir_server/$tmpdir_stap/* $local_tmpdir_stap 2>/dev/null
469 rm -fr $tmpdir_server/$tmpdir_stap
470
471 # Correct the name of the temp directory in the server's stderr output
472 sed -i "s,^Keeping temporary directory.*,Keeping temporary directory \"$local_tmpdir_stap\"," $tmpdir_server/stderr
473 tmpdir_stap=$local_tmpdir_stap
474 else
475 # Make sure we own the systemtap temp directory if we are root.
476 test $EUID = 0 && chown $EUID:$EUID $tmpdir_server/$tmpdir_stap
477 # The temp directory will be moved to here below.
478 tmpdir_stap=`pwd`/$tmpdir_stap
479 fi
480 fi
481
482 # Move the contents of the server's tmpdir down one level to the
483 # current directory (our local server tmpdir)
484 mv $tmpdir_server/* . 2>/dev/null
485 rm -fr $tmpdir_server
486 tmpdir_server=`pwd`
487 }
488
489 # function: find_and_connect_to_server
490 #
491 # Find and establish connection with a compatible stap server.
492 function find_and_connect_to_server {
493 # Use a temp file here instead of a pipeline so that the side effects
494 # of choose_server are seen by the rest of this script.
495 cd $tmpdir_client
496 stap-find-servers > servers
497 choose_server < servers
498 rm -fr servers
499 }
500
501 # function: choose_server
502 #
503 # Examine each line from stdin and attempt to connect to each server
504 # specified until successful.
505 function choose_server {
506 local num_servers=0
507 local name
508 while read name server port remain
509 do
510 num_servers=$(($num_servers + 1))
511
512 if test "X$server" = "X"; then
513 fatal "ERROR: server ip address not provided"
514 fi
515
516 if test "X$port" = "X"; then
517 fatal "ERROR: server port not provided"
518 fi
519
520 if connect_to_server $server $port; then
521 return 0
522 fi
523 done
524
525 if test $num_servers = 0; then
526 fatal "ERROR: unable to find a server"
527 fi
528
529 fatal "ERROR: unable to connect to a server"
530 }
531
532 # function: connect_to_server IP PORT
533 #
534 # Establish connection with the given server
535 function connect_to_server {
536 for ((attempt=0; $attempt < 10; ++attempt))
537 do
538 if echo "request:" | nc -w10 $1 $2 >/dev/null 2>&1; then
539 return 0
540 fi
541 sleep 1
542 done
543
544 return 1
545 }
546
547 # function: disconnect_from_server
548 #
549 # Disconnect from the server.
550 function disconnect_from_server {
551 :
552 }
553
554 # function: process_response
555 #
556 # Write the stdout and stderr from the server to stdout and stderr respectively.
557 function process_response {
558 # Pick up the results of running stap on the server.
559 rc=`cat rc`
560
561 # Copy the module to the current directory, if -m was specified
562 if test "X$m_name" != "X"; then
563 if test -f $tmpdir_stap/$m_name.ko; then
564 cp $tmpdir_stap/$m_name.ko $wd
565 else
566 stream_output
567 fatal "module $tmpdir_stap/$m_name.ko does not exist"
568 fi
569 fi
570
571 # Output stdout and stderr as directed
572 stream_output
573 }
574
575 # function: stream_output
576 #
577 # Output stdout and stderr as directed
578 function stream_output {
579 cd $tmpdir_server
580 cat stderr >&2
581 cat stdout
582 }
583
584 # function: maybe_call_staprun
585 #
586 # Call staprun using the module returned from the server, if requested.
587 function maybe_call_staprun {
588 if test $rc != 0; then
589 # stap run on the server failed, so don't bother
590 return
591 fi
592
593 if test $p_phase -ge 4; then
594 # There should be a systemtap temporary directory.
595 if test "X$tmpdir_stap" = "X"; then
596 # OK if no script specified
597 if test "X$e_script" != "X" -o "X$script_file" != "X"; then
598 fatal "ERROR: systemtap temporary directory is missing in server response"
599 fi
600 return
601 fi
602
603 # There should be a module.
604 local mod_name=`ls $tmpdir_stap | grep '.ko$'`
605 if test "X$mod_name" = "X"; then
606 fatal "ERROR: no module was found in $tmpdir_stap"
607 fi
608
609 if test $p_phase = 5; then
610 test $v_level -gt 0 && echo "Pass 5: starting run." >&2
611
612 # We have a module. Try to run it
613 # If a -c command was specified, pass it along.
614 if test "X$c_cmd" != "X"; then
615 staprun_opts="-c '$c_cmd'"
616 fi
617
618 # The -v level will be one less than what was specified
619 # for us.
620 for ((vl=$((v_level - 1)); $vl > 0; --vl))
621 do
622 staprun_opts="$staprun_opts -v"
623 done
624
625 # if -o was specified, pass it along
626 if test "X$stdout_redirection" != "X"; then
627 staprun_opts="$staprun_opts -o $stdout_redirection"
628 fi
629
630 # Run it in the background and wait for it. This
631 # way any signals send to us can be caught.
632 if test $v_level -ge 2; then
633 echo "running `which staprun` $staprun_opts $tmpdir_stap/`ls $tmpdir_stap | grep '.ko$'`" >&2
634 fi
635 PATH=`staprun_PATH` eval staprun "$staprun_opts" \
636 $tmpdir_stap/`ls $tmpdir_stap | grep '.ko$'`
637 staprun_running=1
638 wait '%?staprun' > /dev/null 2>&1
639 rc=$?
640 staprun_running=0
641 # 127 from wait means that the job was already finished.
642 test $rc=127 && rc=0
643
644 # Wait until the job actually disappears so that its output is complete.
645 while jobs '%?staprun' >/dev/null 2>&1
646 do
647 sleep 1
648 done
649
650 test $v_level -gt 0 && echo "Pass 5: run completed in 0usr/0sys/0real ms." >&2
651 fi
652 fi
653 }
654
655 # function: staprun_PATH
656 #
657 # Compute a PATH suitable for running staprun.
658 function staprun_PATH {
659 # staprun may invoke 'stap'. So we can use the current PATH if we were
660 # not invoked as 'stap' or we are not the first 'stap' on the PATH.
661 local first_stap=`which stap 2>/dev/null`
662 if test `which $0 2>/dev/null` != $first_stap; then
663 echo "$PATH"
664 return
665 fi
666
667 # Otherwise, remove the PATH component where we live from the PATH
668 local PATH_component=`dirname $first_stap`
669 echo "$PATH" | sed "s,$PATH_component,,g"
670 }
671
672 # function: fatal [ MESSAGE ]
673 #
674 # Fatal error
675 # Prints its arguments to stderr and exits
676 function fatal {
677 echo "$0:" "$@" >&2
678 disconnect_from_server
679 cleanup
680 exit 1
681 }
682
683 # function cleanup
684 #
685 # Cleanup work files unless asked to keep them.
686 function cleanup {
687 # Clean up.
688 cd $tmpdir_env
689 if test $keep_temps != 1; then
690 rm -fr $tmpdir_client
691 rm -f $tar_client
692 rm -f $tar_server
693 rm -fr $tmpdir_server
694 fi
695 }
696
697 # function: terminate
698 #
699 # Terminate gracefully.
700 function terminate {
701 # Clean up
702 echo "$0: terminated by signal" >&2
703 cleanup
704
705 # Kill any running staprun job
706 kill -s SIGTERM '%?staprun' 2>/dev/null
707
708 exit 1
709 }
710
711 # function: interrupt
712 #
713 # Pass an interrupt (ctrl-C) to staprun
714 function interrupt {
715 # Pass the signal on to any running staprun job
716 if test $staprun_running = 1; then
717 kill -s SIGINT '%?staprun' 2>/dev/null
718 return
719 fi
720
721 # If staprun was not running, then exit.
722 cleanup
723 exit 1
724 }
725
726 # function: ignore_signal
727 #
728 # Called in order to ignore a signal
729 function ignore_signal {
730 :
731 }
732
733 #-----------------------------------------------------------------------------
734 # Beginning of main line execution.
735 #-----------------------------------------------------------------------------
736 configuration
737 initialization
738 parse_options "$@"
739 create_request
740 package_request
741 find_and_connect_to_server
742 send_request
743 receive_response
744 disconnect_from_server
745 unpack_response
746 process_response
747 maybe_call_staprun
748 cleanup
749
750 exit $rc
This page took 0.071424 seconds and 6 git commands to generate.