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