| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 1 | #!/usr/bin/expect | 
 | 2 |  | 
 | 3 | # This file provides many valuable expect procedures like handle_timeout and | 
 | 4 | # handle_eof. | 
 | 5 |  | 
 | 6 | my_source [list print.tcl] | 
 | 7 |  | 
 | 8 |  | 
 | 9 | proc 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 |  | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 17 |   global spawn_id | 
 | 18 |   global expect_out | 
 | 19 |  | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 20 |   set timeout [get_stack_var timeout {} 2] | 
 | 21 |  | 
 | 22 |   if { $timeout == 1 } { | 
 | 23 |     set seconds "second" | 
 | 24 |   } else { | 
 | 25 |     set seconds "seconds" | 
 | 26 |   } | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 27 |  | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 28 |   puts stderr "" | 
 | 29 |   print_error "Did not get ${description} after $timeout ${seconds}.\n" | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 |   } | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 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 |  | 
 | 43 | proc 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 |  | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 51 |   global spawn_id | 
 | 52 |  | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 53 |   puts stderr "" | 
 | 54 |   print_error "Reached end of file before getting $description.\n" | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 |   } | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 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 | } | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 66 |  | 
 | 67 |  | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 68 | proc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} { | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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. | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 93 |   # fail_on_timeout                 A flag governing the behavior when the | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 94 |   #                                 expect command results in a timeout. If | 
 | 95 |   #                                 set to 1, this procedure will print an | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 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]. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 |   } | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 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 |   } | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 |  | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 163 |  | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 164 | proc expect_wrap_timeout {} { | 
 | 165 |  | 
 | 166 |   # Return constant value of 1000. | 
 | 167 |  | 
 | 168 |   return 1000 | 
 | 169 |  | 
 | 170 | } | 
 | 171 |  | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 172 |  | 
 | 173 | proc 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 | } | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 210 |  | 
 | 211 |  | 
 | 212 | proc 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 | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 216 |   # consisting of 1) the return code of the command 2) the stdout/stderr. | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 217 |  | 
 | 218 |   # It is the caller's responsibility to spawn the appropriate process | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 219 |   # (ssh,telnet) and to get the process to a shell command line (by logging | 
 | 220 |   # in, etc.). | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 221 |  | 
 | 222 |   # Description of argument(s): | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 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. | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 248 |  | 
 | 249 |   # Set defaults (this section allows users to pass blank values for certain | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 250 |   # args). | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 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 |  | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 308 | } |