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