]> 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 #-----------------------------------------------------------------------------
20 # Helper functions.
21 #-----------------------------------------------------------------------------
22 # function: configuration
23 function configuration {
24 tmpdir_prefix_client=stap.client
25 tmpdir_prefix_server=stap.server
26 avahi_service_tag=_stap._tcp
27 }
28
29 # function: initialization
30 function initialization {
31 rc=0
32 wd=`pwd`
33 umask 0
34
35 # Default options settings
36 p_phase=5
37 v_level=0
38 keep_temps=0
39
40 # Create a temporary directory to package things in
41 # Do this before parsing the command line so that there is a place
42 # to put -I and -R directories.
43 tmpdir_client=`mktemp -dt $tmpdir_prefix_client.XXXXXX` || \
44 fatal "ERROR: cannot create temporary directory " $tmpdir_client
45 tmpdir_env=`dirname $tmpdir_client`
46 }
47
48 # function: parse_options [ STAP-OPTIONS ]
49 #
50 # Examine the command line. We need not do much checking, but we do need to
51 # parse all options in order to discover the ones we're interested in.
52 # The server will take care of most situations and return the appropriate
53 # output.
54 #
55 function parse_options {
56 cmdline=
57 cmdline1=
58 cmdline2=
59 while test $# != 0
60 do
61 advance_p=0
62 dash_seen=0
63
64 # Start of a new token.
65 first_token=$1
66 until test $advance_p != 0
67 do
68 # Identify the next option
69 first_char=`expr "$first_token" : '\(.\).*'`
70 second_char=
71 if test $dash_seen = 0; then
72 if test "$first_char" = "-"; then
73 if test "$first_token" != "-"; then
74 # It's not a lone dash, so it's an option.
75 # Is it a long option (i.e. --option)?
76 second_char=`expr "$first_token" : '.\(.\).*'`
77 if test "$second_char" != "-"; then
78 # It's not a lone dash, or a long option, so it's a short option string.
79 # Remove the dash.
80 first_token=`expr "$first_token" : '-\(.*\)'`
81 dash_seen=1
82 first_char=`expr "$first_token" : '\(.\).*'`
83 cmdline2="$cmdline2 -"
84 fi
85 fi
86 fi
87 if test $dash_seen = 0; then
88 # The dash has not been seen. This is either the script file
89 # name, a long argument or an argument to be passed to the probe module.
90 # If this is the first time, and -e has not been specified,
91 # then it could be the name of the script file.
92 if test "X$second_char" = "X-"; then
93 cmdline2="$cmdline2 $first_token"
94 elif test "X$e_script" = "X" -a "X$script_file" = "X"; then
95 script_file=$first_token
96 cmdline1="$cmdline2"
97 cmdline2=
98 else
99 cmdline2="$cmdline2 '$first_token'"
100 fi
101 advance_p=$(($advance_p + 1))
102 break
103 fi
104 fi
105
106 # We are at the start of an option. Look at the first character.
107 case $first_char in
108 c)
109 get_arg $first_token "$2"
110 process_c "$stap_arg"
111 ;;
112 D)
113 get_arg $first_token $2
114 cmdline2="${cmdline2}D '$stap_arg'"
115 ;;
116 e)
117 get_arg $first_token "$2"
118 process_e "$stap_arg"
119 ;;
120 I)
121 get_arg $first_token $2
122 process_I $stap_arg
123 ;;
124 k)
125 keep_temps=1
126 ;;
127 l)
128 get_arg $first_token $2
129 cmdline2="${cmdline2}l '$stap_arg'"
130 ;;
131 m)
132 get_arg $first_token $2
133 cmdline2="${cmdline2}m $stap_arg"
134 ;;
135 o)
136 get_arg $first_token $2
137 process_o $stap_arg
138 ;;
139 p)
140 get_arg $first_token $2
141 process_p $stap_arg
142 ;;
143 r)
144 get_arg $first_token $2
145 cmdline2="${cmdline2}r $stap_arg"
146 ;;
147 R)
148 get_arg $first_token $2
149 process_R $stap_arg
150 ;;
151 s)
152 get_arg $first_token $2
153 cmdline2="${cmdline2}s $stap_arg"
154 ;;
155 v)
156 v_level=$(($v_level + 1))
157 ;;
158 x)
159 get_arg $first_token $2
160 cmdline2="${cmdline2}x $stap_arg"
161 ;;
162 *)
163 # An unknown or unimportant flag. Ignore it, but pass it on to the server.
164 ;;
165 esac
166
167 if test $advance_p = 0; then
168 # Just another flag character. Consume it.
169 cmdline2="$cmdline2$first_char"
170 first_token=`expr "$first_token" : '.\(.*\)'`
171 if test "X$first_token" = "X"; then
172 advance_p=$(($advance_p + 1))
173 fi
174 fi
175 done
176
177 # Consume the arguments we just processed.
178 while test $advance_p != 0
179 do
180 shift
181 advance_p=$(($advance_p - 1))
182 done
183 done
184
185 # If the script file was given and it's not '-', then replace it with its
186 # client-temp-name in the command string.
187 if test "X$script_file" != "X"; then
188 local local_name
189 if test "$script_file" != "-"; then
190 local_name=`generate_client_temp_name $script_file`
191 else
192 local_name=$script_file
193 fi
194 cmdline="$cmdline1 script/$local_name $cmdline2"
195 else
196 cmdline="$cmdline1 $cmdline2"
197 fi
198 }
199
200 # function: get_arg FIRSTWORD SECONDWORD
201 #
202 # Collect an argument to the given option
203 function get_arg {
204 # Remove first character.
205 local opt=`expr "$1" : '\(.\).*'`
206 local first=`expr "$1" : '.\(.*\)'`
207
208 # Advance to the next token, if the first one is exhausted.
209 if test "X$first" = "X"; then
210 shift
211 advance_p=$(($advance_p + 1))
212 first=$1
213 fi
214
215 test "X$first" != "X" || \
216 fatal "Missing argument to -$opt"
217
218 stap_arg="$first"
219 advance_p=$(($advance_p + 1))
220 }
221
222 # function: process_c ARGUMENT
223 #
224 # Process the -c flag.
225 function process_c {
226 c_cmd="$1"
227 cmdline2="${cmdline2}c '$1'"
228 }
229
230 # function: process_e ARGUMENT
231 #
232 # Process the -e flag.
233 function process_e {
234 # Only the first -e option is recognized and it overrides any script file name
235 # which may have already been identified.
236 if test "X$e_script" = "X"; then
237 e_script="$1"
238 if test "X$script_file" != "X"; then
239 cmdline1="$cmdline1 $script_file $cmdline2"
240 cmdline2=
241 script_file=
242 fi
243 fi
244 cmdline2="${cmdline2}e '$1'"
245 }
246
247 # function: process_I ARGUMENT
248 #
249 # Process the -I flag.
250 function process_I {
251 local local_name=`include_file_or_directory tapsets $1`
252 test "X$local_name" != "X" || return
253 cmdline2="${cmdline2}I 'tapsets/$local_name'"
254 }
255
256 # function: process_o ARGUMENT
257 #
258 # Process the -o flag.
259 function process_o {
260 stdout_redirection="> $1"
261 cmdline2="${cmdline2}o '$1'"
262 }
263
264 # function: process_p ARGUMENT
265 #
266 # Process the -p flag.
267 function process_p {
268 p_phase=$1
269 cmdline2="${cmdline2}p '$1'"
270 }
271
272 # function: process_R ARGUMENT
273 #
274 # Process the -R flag.
275 function process_R {
276 local local_name=`include_file_or_directory runtime $1`
277 test "X$local_name" != "X" || return
278 cmdline2="${cmdline2}R 'runtime/$local_name'"
279 }
280
281 # function: include_file_or_directory PREFIX NAME
282 #
283 # Include the given file or directory in the client's temporary
284 # tree to be sent to the server.
285 function include_file_or_directory {
286 # Add a symbolic link of the named file or directory to our temporary directory
287 local local_name=`generate_client_temp_name $2`
288 mkdir -p $tmpdir_client/$1/`dirname $local_name` || \
289 fatal "ERROR: could not create $tmpdir_client/$1/`dirname $local_name`"
290 ln -s /$local_name $tmpdir_client/$1/$local_name || \
291 fatal "ERROR: could not link $tmpdir_client/$1/$local_name to /$local_name"
292 echo "$local_name"
293 }
294
295 # function: generate_client_temp_name NAME
296 #
297 # Generate the name to be used for the given file/directory relative to the
298 # client's temporary directory.
299 function generate_client_temp_name {
300 # Transform the name into a fully qualified path name
301 local full_name=`echo "$1" | sed "s,^\\\([^/]\\\),$wd/\\\\1,"`
302
303 # The same name without the initial / or trailing /
304 local local_name=`echo "$full_name" | sed 's,^/\(.*\),\1,'`
305 local_name=`echo "$local_name" | sed 's,\(.*\)/$,\1,'`
306 echo "$local_name"
307 }
308
309 # function: create_request
310 #
311 # Add information to the client's temp directory representing the request
312 # to the server.
313 function create_request {
314 # Work in our temporary directory
315 cd $tmpdir_client
316
317 if test "X$script_file" != "X"; then
318 if test "$script_file" = "-"; then
319 mkdir -p $tmpdir_client/script || \
320 fatal "ERROR: cannot create temporary directory " $tmpdir_client/script
321 cat > $tmpdir_client/script/$script_file
322 else
323 include_file_or_directory script $script_file > /dev/null
324 fi
325 fi
326
327 # Add the necessary info to special files in our temporary directory.
328 echo "cmdline: $cmdline" > cmdline
329 echo "sysinfo: `client_sysinfo`" > sysinfo
330 }
331
332 # function client_sysinfo
333 #
334 # Generate the client's sysinfo and echo it to stdout
335 function client_sysinfo {
336 if test "X$sysinfo_client" = "X"; then
337 # Add some info from uname
338 sysinfo_client="`uname -rvm`"
339 fi
340 echo "$sysinfo_client"
341 }
342
343 # function: package_request
344 #
345 # Package the client's temp directory into a form suitable for sending to the
346 # server.
347 function package_request {
348 # Package up the temporary directory into a tar file
349 cd $tmpdir_env
350
351 local tmpdir_client_base=`basename $tmpdir_client`
352 tar_client=$tmpdir_env/`mktemp $tmpdir_client_base.tgz.XXXXXX` || \
353 fatal "ERROR: cannot create temporary file " $tar_client
354
355 tar -czhf $tar_client $tmpdir_client_base || \
356 fatal "ERROR: tar of request tree, $tmpdir_client, failed"
357 }
358
359 # function: send_request
360 #
361 # Notify the server and then send $tar_client to the server
362 # The protocol is:
363 # client -> "request:"
364 # server -> "ready:"
365 # client -> $tar_client
366 function send_request {
367 echo "request:" >&3
368 # Get the server's response.
369 read <&3
370 local line=$REPLY
371 check_server_error $line
372
373 # Check for the proper response.
374 test "$line" = "ready:" || \
375 fatal "ERROR: server response, '$line', is incorrect"
376
377 # Send the request file. We need to redirect to /dev/null
378 # in order to workaround a nc bug. It closes the connection
379 # early if stdin from the other side is not provided.
380 until nc $server $(($port + 1)) < $tar_client > /dev/null
381 do
382 sleep 1
383 done
384 }
385
386 # function: receive_response
387 #
388 # Wait for a response from the server indicating the results of our request.
389 # protocol is:
390 # server -> "{done,failed}:"
391 # server -> $tar_server
392 function receive_response {
393 # Get the server's response.
394 read <&3
395 local line=$REPLY
396 check_server_error $line
397
398 # Check for the proper response.
399 if test "$line" != "done:"; then
400 if test "$line" != "failed:"; then
401 fatal "ERROR: server response, '$line', is incorrect"
402 fi
403 rc=1
404 fi
405
406 # Make a place to receive the response file.
407 tar_server=`mktemp -t $tmpdir_prefix_client.server.tgz.XXXXXX` || \
408 fatal "ERROR: cannot create temporary file " $tar_server
409
410 # Retrieve the file. We need to redirect stdin from /dev/zero to work
411 # around a bug in nc. It closes the connection early is stdin is not
412 # provided.
413 until nc $server $(($port + 1)) < /dev/zero > $tar_server
414 do
415 sleep 1
416 done
417 }
418
419 # function: unpack_response
420 #
421 # Unpack the tar file received from the server and make the contents available
422 # for printing the results and/or running 'staprun'.
423 function unpack_response {
424 tmpdir_server=`mktemp -dt $tmpdir_prefix_client.server.XXXXXX` || \
425 fatal "ERROR: cannot create temporary file " $tmpdir_server
426
427 # Unpack the server output directory
428 cd $tmpdir_server
429 tar -xzf $tar_server || \
430 fatal "ERROR: Unpacking of server response, $tar_server, failed"
431
432 # Identify the server's response tree. The tar file should have expanded
433 # into a single directory named to match $tmpdir_prefix_server.??????
434 # which should now be the only item in the current directory.
435 test "`ls | wc -l`" = 1 || \
436 fatal "ERROR: Wrong number of files after expansion of server's tar file"
437
438 tmpdir_server=`ls`
439 tmpdir_server=`expr "$tmpdir_server" : "\\\($tmpdir_prefix_server\\\\.......\\\)"`
440
441 test "X$tmpdir_server" != "X" || \
442 fatal "ERROR: server tar file did not expand as expected"
443
444 # Check the contents of the expanded directory. It should contain:
445 # 1) a file called stdout
446 # 2) a file called stderr
447 # 3) optionally a directory named to match stap??????
448 local num_files=`ls $tmpdir_server | wc -l`
449 test $num_files = 3 -o $num_files = 2 || \
450 fatal "ERROR: Wrong number of files in server's temp directory"
451 test -f $tmpdir_server/stdout || \
452 fatal "ERROR: `pwd`/$tmpdir_server/stdout does not exist or is not a regular file"
453 test -f $tmpdir_server/stderr || \
454 fatal "ERROR: `pwd`/$tmpdir_server/stderr 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 tmpdir_stap=`pwd`/$tmpdir_stap
476 fi
477
478 # Make sure we own the systemtap temp directory if we are root.
479 test $EUID = 0 && chown $EUID:$EUID $tmpdir_stap
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: cannot 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 # Open a connection to the server
537 exec 3<> /dev/tcp/$1/$2
538 }
539
540 # function: disconnect_from_server
541 #
542 # Disconnect from the server.
543 function disconnect_from_server {
544 # Close the connection to the server.
545 exec 3<&-
546 }
547
548 # function: stream_output
549 #
550 # Write the stdout and stderr from the server to stdout and stderr respectively.
551 function stream_output {
552 # Output stdout and stderr as directed
553 cd $tmpdir_server
554 cat stderr >&2
555 eval cat stdout $stdout_redirection
556 }
557
558 # function: maybe_call_staprun
559 #
560 # Call staprun using the module returned from the server, if requested.
561 function maybe_call_staprun {
562 if test $rc != 0; then
563 # stap run on the server failed, so don't bother
564 return
565 fi
566
567 if test $p_phase -ge 4; then
568 # There should be a systemtap temporary directory.
569 if test "X$tmpdir_stap" = "X"; then
570 # OK if no script specified
571 if test "X$script_file" != "X"; then
572 fatal "ERROR: systemtap temporary directory is missing in server response"
573 fi
574 return
575 fi
576
577 # There should be a module.
578 local mod_name=`ls $tmpdir_stap | grep '.ko$'`
579 if test "X$mod_name" = "X"; then
580 fatal "ERROR: no module was found in $tmpdir_stap"
581 fi
582
583 if test $p_phase = 5; then
584 # We have a module. Try to run it
585 for ((--v_level; $v_level > 0; --v_level))
586 do
587 staprun_opts="$staprun_opts -v"
588 done
589 PATH=`staprun_PATH` staprun $staprun_opts \
590 $tmpdir_stap/`ls $tmpdir_stap | grep '.ko$'`
591 rc=$?
592 fi
593 fi
594 }
595
596 # function: staprun_PATH
597 #
598 # Compute a PATH suitable for running staprun.
599 function staprun_PATH {
600 # staprun may invoke 'stap'. So we can use the current PATH if we were
601 # not invoked as 'stap' or we are not the first 'stap' on the PATH.
602 local first_stap=`which stap 2>/dev/null`
603 if test `which $0 2>/dev/null` != $first_stap; then
604 echo "$PATH"
605 return
606 fi
607
608 # Otherwise, remove the PATH component where we live from the PATH
609 local PATH_component=`dirname $first_stap`
610 echo "$PATH" | sed "s,$PATH_component,,g"
611 }
612
613 # function: check_server_error SERVER_RESPONSE
614 #
615 # Check the given server response for an error message.
616 function check_server_error {
617 echo "$1" | grep -q "^ERROR:" && \
618 server_fatal "Server:" "$@"
619 }
620
621 # function: fatal [ MESSAGE ]
622 #
623 # Fatal error
624 # Prints its arguments to stderr and exits
625 function fatal {
626 echo "$0:" "$@" >&2
627 disconnect_from_server
628 cleanup
629 exit 1
630 }
631
632 # function: server_fatal [ MESSAGE ]
633 #
634 # Fatal error
635 # Prints its arguments to stderr and exits
636 function server_fatal {
637 echo "$0:" "$@" >&2
638 cat <&3 >&2
639 disconnect_from_server
640 cleanup
641 exit 1
642 }
643
644 # function cleanup
645 #
646 # Cleanup work files unless asked to keep them.
647 function cleanup {
648 # Clean up.
649 cd $tmpdir_env
650 if test $keep_temps != 1; then
651 rm -fr $tmpdir_client
652 rm -f $tar_client
653 rm -f $tar_server
654 rm -fr $tmpdir_server
655 fi
656 }
657
658 #-----------------------------------------------------------------------------
659 # Beginning of main line execution.
660 #-----------------------------------------------------------------------------
661 configuration
662 initialization
663 parse_options "$@"
664 create_request
665 package_request
666 find_and_connect_to_server
667 send_request
668 receive_response
669 unpack_response
670 disconnect_from_server
671 stream_output
672 maybe_call_staprun
673 cleanup
674
675 exit $rc
This page took 0.073027 seconds and 6 git commands to generate.