| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 1 | #!/usr/bin/expect | 
|  | 2 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 3 | # This file provides many valuable expect procedures like handle_timeout and handle_eof. | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 4 |  | 
|  | 5 | my_source [list print.tcl] | 
|  | 6 |  | 
|  | 7 |  | 
|  | 8 | proc handle_timeout { description } { | 
|  | 9 |  | 
|  | 10 | # Print timeout error message to stderr and exit 1. | 
|  | 11 |  | 
|  | 12 | # Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 13 | # description                     A description of what was being expected (e.g. "an SOL login prompt"). | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 14 |  | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 15 | global spawn_id | 
|  | 16 | global expect_out | 
|  | 17 |  | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 18 | set timeout [get_stack_var timeout {} 2] | 
|  | 19 |  | 
|  | 20 | if { $timeout == 1 } { | 
|  | 21 | set seconds "second" | 
|  | 22 | } else { | 
|  | 23 | set seconds "seconds" | 
|  | 24 | } | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 25 |  | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 26 | puts stderr "" | 
|  | 27 | print_error "Did not get ${description} after $timeout ${seconds}.\n" | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 | } | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 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 |  | 
|  | 41 | proc handle_eof { description } { | 
|  | 42 |  | 
|  | 43 | # Print end-of-file error message to stderr and exit 1. | 
|  | 44 |  | 
|  | 45 | # Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 46 | # description                     A description of what was being expected (e.g. "an SOL login prompt"). | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 47 |  | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 48 | global spawn_id | 
|  | 49 |  | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 50 | puts stderr "" | 
|  | 51 | print_error "Reached end of file before getting $description.\n" | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 | } | 
| Michael Walsh | aa245bb | 2018-02-20 11:30:00 -0600 | [diff] [blame] | 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 | } | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 63 |  | 
|  | 64 |  | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 65 | proc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} { | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 66 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 67 | # Run the expect command for the caller and return the list index of the matching pattern. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 68 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 72 | # - It will do standardized timeout and eof handling. | 
|  | 73 |  | 
|  | 74 | # Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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"). | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 81 | #                                 This will be included in output messages. | 
|  | 82 | # timeout                         The expect timeout value. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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]. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 { | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 118 | # Check to see whether the caller has specified a flag (e.g. "-re", "-ex", etc.) at the beginning of the | 
|  | 119 | # pattern. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 | } | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 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 | } | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 |  | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 150 |  | 
| Joy Onyerikwu | 19d33f5 | 2018-03-05 15:30:56 -0600 | [diff] [blame] | 151 | proc expect_wrap_timeout {} { | 
|  | 152 |  | 
|  | 153 | # Return constant value of 1000. | 
|  | 154 |  | 
|  | 155 | return 1000 | 
|  | 156 |  | 
|  | 157 | } | 
|  | 158 |  | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 159 |  | 
|  | 160 | proc send_wrap {buffer {add_lf 1}} { | 
|  | 161 |  | 
|  | 162 | # Send the buffer to the spawned process. | 
|  | 163 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 167 |  | 
|  | 168 | # Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 169 | # buffer                          The string to be sent to the spawned process. | 
| Michael Walsh | a0a4240 | 2018-02-27 10:50:40 -0600 | [diff] [blame] | 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 | } | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 195 |  | 
|  | 196 |  | 
|  | 197 | proc shell_command {command_string {prompt_regex} { quiet {} } \ | 
|  | 198 | { test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} { | 
|  | 199 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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. | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 202 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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.). | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 205 |  | 
|  | 206 | # Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 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. | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 222 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 223 | # Set defaults (this section allows users to pass blank values for certain args). | 
| Joy Onyerikwu | 56fd36a | 2018-04-10 10:09:07 -0500 | [diff] [blame] | 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 |  | 
| Michael Walsh | e889932 | 2018-04-18 11:45:24 -0500 | [diff] [blame] | 281 | } |