blob: 0fb474f2e38e8be1e4ffdc5878660e22567708c0 [file] [log] [blame]
#!/usr/bin/expect
# This file provides many valuable expect procedures like handle_timeout and handle_eof.
my_source [list print.tcl]
proc handle_timeout { description } {
# Print timeout error message to stderr and exit 1.
# Description of argument(s):
# description A description of what was being expected (e.g. "an SOL login prompt").
global spawn_id
global expect_out
set timeout [get_stack_var timeout {} 2]
if { $timeout == 1 } {
set seconds "second"
} else {
set seconds "seconds"
}
puts stderr ""
print_error "Did not get ${description} after $timeout ${seconds}.\n"
# Using uplevel to be able to access expect_out.
if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } {
puts stderr [sprint_varx expect_out "<not set>"]
}
# If caller has exit_proc defined, call it. Otherwise, just call exit.
if { [info procs "exit_proc"] != "" } {
exit_proc 1
}
exit 1
}
proc handle_eof { description } {
# Print end-of-file error message to stderr and exit 1.
# Description of argument(s):
# description A description of what was being expected (e.g. "an SOL login prompt").
global spawn_id
puts stderr ""
print_error "Reached end of file before getting $description.\n"
# Using uplevel to be able to access expect_out.
if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } {
puts stderr [sprint_varx expect_out "<not set>"]
}
# If caller has exit_proc defined, call it. Otherwise, just call exit.
if { [info procs "exit_proc"] != "" } {
exit_proc 1
}
exit 1
}
proc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} {
# Run the expect command for the caller and return the list index of the matching pattern.
# This function offers the following benefits over calling the expect command directly:
# - It makes program debug easier. When the program is run with --debug=1, this function prints useful
# debug output.
# - It will do standardized timeout and eof handling.
# Description of argument(s):
# pattern_list A list of patterns to be matched. If one of the patterns matches, the
# list index of the matching item will be returned. By default, each
# pattern is presumed to be a regex. If the caller wishes to, they may
# precede each pattern with either of the following: "-re ", "-gl " or "-ex
# " in order to explicitly choose the kind of match to be done..
# message A message explaining what is being expected (e.g. "an SOL login prompt").
# This will be included in output messages.
# timeout The expect timeout value.
# fail_on_timeout A flag governing the behavior when the expect command results in a
# timeout. If set to 1, this procedure will print an error message to
# standard error and exit the program with a non-zero return code. If set
# to 0, it will return [expect_wrap_timeout].
# Example usage:
# set result [expect_wrap\
# [list $bad_user_pw_regex "sh: xauth: command not found"]\
# "an SOL prompt" 10]
#
# switch $result {
# 0 {
# puts stderr "" ; print_error "Invalid username or password.\n"
# exit_proc 1
# }
# 1 {
# dict set state ssh_logged_in 1
# }
# }
global spawn_id
global expect_out
# Recognized flags.
set flags [list "-re" "-ex" "-gl"]
# This helps debug efforts by removing leftover, stale entries.
array unset expect_out \[1-9\],string
# Prepare the expect statement.
append cmd_buf "global spawn_id\n"
append cmd_buf "global expect_out\n"
append cmd_buf "expect {\n"
set ix 0
foreach pattern $pattern_list {
# Check to see whether the caller has specified a flag (e.g. "-re", "-ex", etc.) at the beginning of the
# pattern.
set tokens [split $pattern " "]
if { [lsearch $flags [lindex $tokens 0]] != -1 } {
# Caller specified a flag.
set flag [lindex $tokens 0]
# Strip the flag from the pattern.
set pattern [string range $pattern 4 end]
} else {
set flag "-re"
}
append cmd_buf " ${flag} {$pattern} {set expect_result $ix}\n"
incr ix
}
if { $fail_on_timeout } {
append cmd_buf " timeout {handle_timeout \$message}\n"
} else {
append cmd_buf " timeout {set expect_result \[expect_wrap_timeout\]}\n"
}
append cmd_buf " eof {handle_eof \$message}\n"
append cmd_buf "}\n"
dprint_timen "Expecting $message."
dprint_issuing "\n${cmd_buf}"
eval ${cmd_buf}
dprintn ; dprint_vars expect_out expect_result
return $expect_result
}
proc expect_wrap_timeout {} {
# Return constant value of 1000.
return 1000
}
proc send_wrap {buffer {add_lf 1}} {
# Send the buffer to the spawned process.
# This function offers the following benefits over calling the send command directly:
# - It makes program debug easier. When the program is run with --debug=1, this function prints useful
# debug output.
# Description of argument(s):
# buffer The string to be sent to the spawned process.
# add_lf Send a line feed after sending the buffer.
# Example usage.
# Close the ssh session.
# send_wrap "~."
#
# set expect_result [expect_wrap\
# [list "Connection to $host closed"]\
# "a connection closed message" 5]
global spawn_id
global expect_out
set cmd_buf "send -- {${buffer}}"
dprint_issuing
eval ${cmd_buf}
if { $add_lf } {
send -- "\n"
set cmd_buf "send -- \"\\n\""
dprint_issuing
eval ${cmd_buf}
}
}
proc shell_command {command_string {prompt_regex} { quiet {} } \
{ test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
# Execute the command_string on the shell command line and return a list consisting of 1) the return code
# of the command 2) the stdout/stderr.
# It is the caller's responsibility to spawn the appropriate process (ssh,telnet) and to get the process
# to a shell command line (by logging in, etc.).
# Description of argument(s):
# command_string The command string which is to be run on the shell (e.g. "hostname" or
# "grep this that").
# prompt_regex A regular expression to match the prompt for current shell to run on (e.g
# "/ #").
# quiet Indicates whether this procedure should run the print_issuing() procedure
# which prints "Issuing: <cmd string>" to stdout. The default value is 0.
# test_mode If test_mode is set, this procedure will not actually run the command.
# If print_output is set, it will print "(test_mode) Issuing: <cmd string>"
# to stdout. The default value is 0.
# show_err If show_err is set, this procedure will print a standardized error report
# if the shell command returns non-zero. The default value is 1.
# ignore_err If ignore_err is set, this procedure will not fail if the shell command
# fails. However, if ignore_err is not set, this procedure will exit 1 if
# the shell command fails. The default value is 1.
# trim_cr_lf Trim any trailing carriage return or line feed from the result.
# Set defaults (this section allows users to pass blank values for certain args).
set_var_default quiet [get_stack_var quiet 0 2]
set_var_default test_mode 0
set_var_default show_err 1
set_var_default ignore_err 0
set_var_default acceptable_shell_rcs 0
global spawn_id
global expect_out
qprintn ; qprint_issuing ${command_string} ${test_mode}
if { $test_mode } {
return [list 0 ""]
}
send_wrap "${command_string}"
set expect_result [expect_wrap\
[list "-ex $command_string"]\
"the echoed command" 5]
set expect_result [expect_wrap\
[list {[\n\r]{1,2}}]\
"one or two line feeds" 5]
# Note the non-greedy specification in the regex below (the "?").
set expect_result [expect_wrap\
[list "(.*?)$prompt_regex"]\
"command output plus prompt" -1]
# The command's stdout/stderr should be captured as match #1.
set out_buf $expect_out(1,string)
if { $trim_cr_lf } {
set out_buf [ string trimright $out_buf "\r\n" ]
}
# Get rc via recursive call to this function.
set rc 0
set proc_name [get_stack_proc_name]
set calling_proc_name [get_stack_proc_name -2]
if { $calling_proc_name != $proc_name } {
set sub_result [shell_command {echo ${?}} $prompt_regex 1]
dprintn ; dprint_list sub_result
set rc [lindex $sub_result 1]
}
if { $rc != 0 } {
if { $show_err } {
puts stderr "" ; print_error_report "The prior shell command failed.\n"
}
if { ! $ignore_err } {
if { [info procs "exit_proc"] != "" } {
exit_proc 1
}
}
}
return [list $rc $out_buf]
}