blob: 0fb474f2e38e8be1e4ffdc5878660e22567708c0 [file] [log] [blame]
Michael Walshaa245bb2018-02-20 11:30:00 -06001#!/usr/bin/expect
2
Michael Walsh410b1782019-10-22 15:56:18 -05003# This file provides many valuable expect procedures like handle_timeout and handle_eof.
Michael Walshaa245bb2018-02-20 11:30:00 -06004
5my_source [list print.tcl]
6
7
8proc handle_timeout { description } {
9
10 # Print timeout error message to stderr and exit 1.
11
12 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050013 # description A description of what was being expected (e.g. "an SOL login prompt").
Michael Walshaa245bb2018-02-20 11:30:00 -060014
Michael Walsha0a42402018-02-27 10:50:40 -060015 global spawn_id
16 global expect_out
17
Michael Walshaa245bb2018-02-20 11:30:00 -060018 set timeout [get_stack_var timeout {} 2]
19
20 if { $timeout == 1 } {
21 set seconds "second"
22 } else {
23 set seconds "seconds"
24 }
Michael Walsha0a42402018-02-27 10:50:40 -060025
Michael Walshaa245bb2018-02-20 11:30:00 -060026 puts stderr ""
27 print_error "Did not get ${description} after $timeout ${seconds}.\n"
Michael Walsha0a42402018-02-27 10:50:40 -060028 # Using uplevel to be able to access expect_out.
29 if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } {
30 puts stderr [sprint_varx expect_out "<not set>"]
31 }
Michael Walshaa245bb2018-02-20 11:30:00 -060032 # If caller has exit_proc defined, call it. Otherwise, just call exit.
33 if { [info procs "exit_proc"] != "" } {
34 exit_proc 1
35 }
36 exit 1
37
38}
39
40
41proc handle_eof { description } {
42
43 # Print end-of-file error message to stderr and exit 1.
44
45 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050046 # description A description of what was being expected (e.g. "an SOL login prompt").
Michael Walshaa245bb2018-02-20 11:30:00 -060047
Michael Walsha0a42402018-02-27 10:50:40 -060048 global spawn_id
49
Michael Walshaa245bb2018-02-20 11:30:00 -060050 puts stderr ""
51 print_error "Reached end of file before getting $description.\n"
Michael Walsha0a42402018-02-27 10:50:40 -060052 # Using uplevel to be able to access expect_out.
53 if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } {
54 puts stderr [sprint_varx expect_out "<not set>"]
55 }
Michael Walshaa245bb2018-02-20 11:30:00 -060056 # If caller has exit_proc defined, call it. Otherwise, just call exit.
57 if { [info procs "exit_proc"] != "" } {
58 exit_proc 1
59 }
60 exit 1
61
62}
Michael Walsha0a42402018-02-27 10:50:40 -060063
64
Joy Onyerikwu19d33f52018-03-05 15:30:56 -060065proc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} {
Michael Walsha0a42402018-02-27 10:50:40 -060066
Michael Walsh410b1782019-10-22 15:56:18 -050067 # Run the expect command for the caller and return the list index of the matching pattern.
Michael Walsha0a42402018-02-27 10:50:40 -060068
Michael Walsh410b1782019-10-22 15:56:18 -050069 # This function offers the following benefits over calling the expect command directly:
70 # - It makes program debug easier. When the program is run with --debug=1, this function prints useful
71 # debug output.
Michael Walsha0a42402018-02-27 10:50:40 -060072 # - It will do standardized timeout and eof handling.
73
74 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050075 # pattern_list A list of patterns to be matched. If one of the patterns matches, the
76 # list index of the matching item will be returned. By default, each
77 # pattern is presumed to be a regex. If the caller wishes to, they may
78 # precede each pattern with either of the following: "-re ", "-gl " or "-ex
79 # " in order to explicitly choose the kind of match to be done..
80 # message A message explaining what is being expected (e.g. "an SOL login prompt").
Michael Walsha0a42402018-02-27 10:50:40 -060081 # This will be included in output messages.
82 # timeout The expect timeout value.
Michael Walsh410b1782019-10-22 15:56:18 -050083 # fail_on_timeout A flag governing the behavior when the expect command results in a
84 # timeout. If set to 1, this procedure will print an error message to
85 # standard error and exit the program with a non-zero return code. If set
86 # to 0, it will return [expect_wrap_timeout].
Michael Walsha0a42402018-02-27 10:50:40 -060087
88 # Example usage:
89 # set result [expect_wrap\
90 # [list $bad_user_pw_regex "sh: xauth: command not found"]\
91 # "an SOL prompt" 10]
92 #
93 # switch $result {
94 # 0 {
95 # puts stderr "" ; print_error "Invalid username or password.\n"
96 # exit_proc 1
97 # }
98 # 1 {
99 # dict set state ssh_logged_in 1
100 # }
101 # }
102
103 global spawn_id
104 global expect_out
105
106 # Recognized flags.
107 set flags [list "-re" "-ex" "-gl"]
108
109 # This helps debug efforts by removing leftover, stale entries.
110 array unset expect_out \[1-9\],string
111
112 # Prepare the expect statement.
113 append cmd_buf "global spawn_id\n"
114 append cmd_buf "global expect_out\n"
115 append cmd_buf "expect {\n"
116 set ix 0
117 foreach pattern $pattern_list {
Michael Walsh410b1782019-10-22 15:56:18 -0500118 # Check to see whether the caller has specified a flag (e.g. "-re", "-ex", etc.) at the beginning of the
119 # pattern.
Michael Walsha0a42402018-02-27 10:50:40 -0600120 set tokens [split $pattern " "]
121 if { [lsearch $flags [lindex $tokens 0]] != -1 } {
122 # Caller specified a flag.
123 set flag [lindex $tokens 0]
124 # Strip the flag from the pattern.
125 set pattern [string range $pattern 4 end]
126 } else {
127 set flag "-re"
128 }
129 append cmd_buf " ${flag} {$pattern} {set expect_result $ix}\n"
130 incr ix
131 }
Joy Onyerikwu19d33f52018-03-05 15:30:56 -0600132 if { $fail_on_timeout } {
133 append cmd_buf " timeout {handle_timeout \$message}\n"
134 } else {
135 append cmd_buf " timeout {set expect_result \[expect_wrap_timeout\]}\n"
136 }
Michael Walsha0a42402018-02-27 10:50:40 -0600137 append cmd_buf " eof {handle_eof \$message}\n"
138 append cmd_buf "}\n"
139
140 dprint_timen "Expecting $message."
141 dprint_issuing "\n${cmd_buf}"
142 eval ${cmd_buf}
143
144 dprintn ; dprint_vars expect_out expect_result
145
146 return $expect_result
147
148}
149
Michael Walshe8899322018-04-18 11:45:24 -0500150
Joy Onyerikwu19d33f52018-03-05 15:30:56 -0600151proc expect_wrap_timeout {} {
152
153 # Return constant value of 1000.
154
155 return 1000
156
157}
158
Michael Walsha0a42402018-02-27 10:50:40 -0600159
160proc send_wrap {buffer {add_lf 1}} {
161
162 # Send the buffer to the spawned process.
163
Michael Walsh410b1782019-10-22 15:56:18 -0500164 # This function offers the following benefits over calling the send command directly:
165 # - It makes program debug easier. When the program is run with --debug=1, this function prints useful
166 # debug output.
Michael Walsha0a42402018-02-27 10:50:40 -0600167
168 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500169 # buffer The string to be sent to the spawned process.
Michael Walsha0a42402018-02-27 10:50:40 -0600170 # add_lf Send a line feed after sending the buffer.
171
172 # Example usage.
173 # Close the ssh session.
174 # send_wrap "~."
175 #
176 # set expect_result [expect_wrap\
177 # [list "Connection to $host closed"]\
178 # "a connection closed message" 5]
179
180 global spawn_id
181 global expect_out
182
183 set cmd_buf "send -- {${buffer}}"
184 dprint_issuing
185 eval ${cmd_buf}
186
187 if { $add_lf } {
188 send -- "\n"
189 set cmd_buf "send -- \"\\n\""
190 dprint_issuing
191 eval ${cmd_buf}
192 }
193
194}
Joy Onyerikwu56fd36a2018-04-10 10:09:07 -0500195
196
197proc shell_command {command_string {prompt_regex} { quiet {} } \
198 { test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
199
Michael Walsh410b1782019-10-22 15:56:18 -0500200 # Execute the command_string on the shell command line and return a list consisting of 1) the return code
201 # of the command 2) the stdout/stderr.
Joy Onyerikwu56fd36a2018-04-10 10:09:07 -0500202
Michael Walsh410b1782019-10-22 15:56:18 -0500203 # It is the caller's responsibility to spawn the appropriate process (ssh,telnet) and to get the process
204 # to a shell command line (by logging in, etc.).
Joy Onyerikwu56fd36a2018-04-10 10:09:07 -0500205
206 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500207 # command_string The command string which is to be run on the shell (e.g. "hostname" or
208 # "grep this that").
209 # prompt_regex A regular expression to match the prompt for current shell to run on (e.g
210 # "/ #").
211 # quiet Indicates whether this procedure should run the print_issuing() procedure
212 # which prints "Issuing: <cmd string>" to stdout. The default value is 0.
213 # test_mode If test_mode is set, this procedure will not actually run the command.
214 # If print_output is set, it will print "(test_mode) Issuing: <cmd string>"
215 # to stdout. The default value is 0.
216 # show_err If show_err is set, this procedure will print a standardized error report
217 # if the shell command returns non-zero. The default value is 1.
218 # ignore_err If ignore_err is set, this procedure will not fail if the shell command
219 # fails. However, if ignore_err is not set, this procedure will exit 1 if
220 # the shell command fails. The default value is 1.
221 # trim_cr_lf Trim any trailing carriage return or line feed from the result.
Joy Onyerikwu56fd36a2018-04-10 10:09:07 -0500222
Michael Walsh410b1782019-10-22 15:56:18 -0500223 # Set defaults (this section allows users to pass blank values for certain args).
Joy Onyerikwu56fd36a2018-04-10 10:09:07 -0500224 set_var_default quiet [get_stack_var quiet 0 2]
225 set_var_default test_mode 0
226 set_var_default show_err 1
227 set_var_default ignore_err 0
228 set_var_default acceptable_shell_rcs 0
229
230 global spawn_id
231 global expect_out
232
233 qprintn ; qprint_issuing ${command_string} ${test_mode}
234
235 if { $test_mode } {
236 return [list 0 ""]
237 }
238
239 send_wrap "${command_string}"
240
241 set expect_result [expect_wrap\
242 [list "-ex $command_string"]\
243 "the echoed command" 5]
244 set expect_result [expect_wrap\
245 [list {[\n\r]{1,2}}]\
246 "one or two line feeds" 5]
247 # Note the non-greedy specification in the regex below (the "?").
248 set expect_result [expect_wrap\
249 [list "(.*?)$prompt_regex"]\
250 "command output plus prompt" -1]
251 # The command's stdout/stderr should be captured as match #1.
252 set out_buf $expect_out(1,string)
253
254 if { $trim_cr_lf } {
255 set out_buf [ string trimright $out_buf "\r\n" ]
256 }
257
258 # Get rc via recursive call to this function.
259 set rc 0
260 set proc_name [get_stack_proc_name]
261 set calling_proc_name [get_stack_proc_name -2]
262 if { $calling_proc_name != $proc_name } {
263 set sub_result [shell_command {echo ${?}} $prompt_regex 1]
264 dprintn ; dprint_list sub_result
265 set rc [lindex $sub_result 1]
266 }
267
268 if { $rc != 0 } {
269 if { $show_err } {
270 puts stderr "" ; print_error_report "The prior shell command failed.\n"
271 }
272 if { ! $ignore_err } {
273 if { [info procs "exit_proc"] != "" } {
274 exit_proc 1
275 }
276 }
277 }
278
279 return [list $rc $out_buf]
280
Michael Walshe8899322018-04-18 11:45:24 -0500281}