1#!/usr/bin/expect
2
3# This file provides many valuable expect procedures like handle_timeout and handle_eof.
4
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):
13  # description                     A description of what was being expected (e.g. "an SOL login prompt").
14
15  global spawn_id
16  global expect_out
17
18  set timeout [get_stack_var timeout {} 2]
19
20  if { $timeout == 1 } {
21    set seconds "second"
22  } else {
23    set seconds "seconds"
24  }
25
26  puts stderr ""
27  print_error "Did not get ${description} after $timeout ${seconds}.\n"
28  # 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  }
32  # 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):
46  # description                     A description of what was being expected (e.g. "an SOL login prompt").
47
48  global spawn_id
49
50  puts stderr ""
51  print_error "Reached end of file before getting $description.\n"
52  # 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  }
56  # 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}
63
64
65proc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} {
66
67  # Run the expect command for the caller and return the list index of the matching pattern.
68
69  # 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.
72  # - It will do standardized timeout and eof handling.
73
74  # Description of argument(s):
75  # 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").
81  #                                 This will be included in output messages.
82  # timeout                         The expect timeout value.
83  # 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].
87
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 {
118    # Check to see whether the caller has specified a flag (e.g. "-re", "-ex", etc.) at the beginning of the
119    # pattern.
120    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  }
132  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  }
137  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
150
151proc expect_wrap_timeout {} {
152
153  # Return constant value of 1000.
154
155  return 1000
156
157}
158
159
160proc send_wrap {buffer {add_lf 1}} {
161
162  # Send the buffer to the spawned process.
163
164  # 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.
167
168  # Description of argument(s):
169  # buffer                          The string to be sent to the spawned process.
170  # 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}
195
196
197proc shell_command {command_string {prompt_regex} { quiet {} } \
198  { test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
199
200  # 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.
202
203  # 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.).
205
206  # Description of argument(s):
207  # 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.
222
223  # Set defaults (this section allows users to pass blank values for certain args).
224  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
281}
282