1aa245bbfSMichael Walsh#!/usr/bin/expect
2aa245bbfSMichael Walsh
3*410b1787SMichael Walsh# This file provides many valuable expect procedures like handle_timeout and handle_eof.
4aa245bbfSMichael Walsh
5aa245bbfSMichael Walshmy_source [list print.tcl]
6aa245bbfSMichael Walsh
7aa245bbfSMichael Walsh
8aa245bbfSMichael Walshproc handle_timeout { description } {
9aa245bbfSMichael Walsh
10aa245bbfSMichael Walsh  # Print timeout error message to stderr and exit 1.
11aa245bbfSMichael Walsh
12aa245bbfSMichael Walsh  # Description of argument(s):
13*410b1787SMichael Walsh  # description                     A description of what was being expected (e.g. "an SOL login prompt").
14aa245bbfSMichael Walsh
15a0a42403SMichael Walsh  global spawn_id
16a0a42403SMichael Walsh  global expect_out
17a0a42403SMichael Walsh
18aa245bbfSMichael Walsh  set timeout [get_stack_var timeout {} 2]
19aa245bbfSMichael Walsh
20aa245bbfSMichael Walsh  if { $timeout == 1 } {
21aa245bbfSMichael Walsh    set seconds "second"
22aa245bbfSMichael Walsh  } else {
23aa245bbfSMichael Walsh    set seconds "seconds"
24aa245bbfSMichael Walsh  }
25a0a42403SMichael Walsh
26aa245bbfSMichael Walsh  puts stderr ""
27aa245bbfSMichael Walsh  print_error "Did not get ${description} after $timeout ${seconds}.\n"
28a0a42403SMichael Walsh  # Using uplevel to be able to access expect_out.
29a0a42403SMichael Walsh  if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } {
30a0a42403SMichael Walsh    puts stderr [sprint_varx expect_out "<not set>"]
31a0a42403SMichael Walsh  }
32aa245bbfSMichael Walsh  # If caller has exit_proc defined, call it.  Otherwise, just call exit.
33aa245bbfSMichael Walsh  if { [info procs "exit_proc"] != "" } {
34aa245bbfSMichael Walsh    exit_proc 1
35aa245bbfSMichael Walsh  }
36aa245bbfSMichael Walsh  exit 1
37aa245bbfSMichael Walsh
38aa245bbfSMichael Walsh}
39aa245bbfSMichael Walsh
40aa245bbfSMichael Walsh
41aa245bbfSMichael Walshproc handle_eof { description } {
42aa245bbfSMichael Walsh
43aa245bbfSMichael Walsh  # Print end-of-file error message to stderr and exit 1.
44aa245bbfSMichael Walsh
45aa245bbfSMichael Walsh  # Description of argument(s):
46*410b1787SMichael Walsh  # description                     A description of what was being expected (e.g. "an SOL login prompt").
47aa245bbfSMichael Walsh
48a0a42403SMichael Walsh  global spawn_id
49a0a42403SMichael Walsh
50aa245bbfSMichael Walsh  puts stderr ""
51aa245bbfSMichael Walsh  print_error "Reached end of file before getting $description.\n"
52a0a42403SMichael Walsh  # Using uplevel to be able to access expect_out.
53a0a42403SMichael Walsh  if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } {
54a0a42403SMichael Walsh    puts stderr [sprint_varx expect_out "<not set>"]
55a0a42403SMichael Walsh  }
56aa245bbfSMichael Walsh  # If caller has exit_proc defined, call it.  Otherwise, just call exit.
57aa245bbfSMichael Walsh  if { [info procs "exit_proc"] != "" } {
58aa245bbfSMichael Walsh    exit_proc 1
59aa245bbfSMichael Walsh  }
60aa245bbfSMichael Walsh  exit 1
61aa245bbfSMichael Walsh
62aa245bbfSMichael Walsh}
63a0a42403SMichael Walsh
64a0a42403SMichael Walsh
6519d33f5dSJoy Onyerikwuproc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} {
66a0a42403SMichael Walsh
67*410b1787SMichael Walsh  # Run the expect command for the caller and return the list index of the matching pattern.
68a0a42403SMichael Walsh
69*410b1787SMichael Walsh  # This function offers the following benefits over calling the expect command directly:
70*410b1787SMichael Walsh  # - It makes program debug easier.  When the program is run with --debug=1, this function prints useful
71*410b1787SMichael Walsh  #   debug output.
72a0a42403SMichael Walsh  # - It will do standardized timeout and eof handling.
73a0a42403SMichael Walsh
74a0a42403SMichael Walsh  # Description of argument(s):
75*410b1787SMichael Walsh  # pattern_list                    A list of patterns to be matched.  If one of the patterns matches, the
76*410b1787SMichael Walsh  #                                 list index of the matching item will be returned.  By default, each
77*410b1787SMichael Walsh  #                                 pattern is presumed to be a regex.  If the caller wishes to, they may
78*410b1787SMichael Walsh  #                                 precede each pattern with either of the following: "-re ", "-gl " or "-ex
79*410b1787SMichael Walsh  #                                 " in order to explicitly choose the kind of match to be done..
80*410b1787SMichael Walsh  # message                         A message explaining what is being expected (e.g. "an SOL login prompt").
81a0a42403SMichael Walsh  #                                 This will be included in output messages.
82a0a42403SMichael Walsh  # timeout                         The expect timeout value.
83*410b1787SMichael Walsh  # fail_on_timeout                 A flag governing the behavior when the expect command results in a
84*410b1787SMichael Walsh  #                                 timeout. If set to 1, this procedure will print an error message to
85*410b1787SMichael Walsh  #                                 standard error and exit the program with a non-zero return code. If set
86*410b1787SMichael Walsh  #                                 to 0, it will return [expect_wrap_timeout].
87a0a42403SMichael Walsh
88a0a42403SMichael Walsh  # Example usage:
89a0a42403SMichael Walsh  #   set result [expect_wrap\
90a0a42403SMichael Walsh  #     [list $bad_user_pw_regex "sh: xauth: command not found"]\
91a0a42403SMichael Walsh  #     "an SOL prompt" 10]
92a0a42403SMichael Walsh  #
93a0a42403SMichael Walsh  #   switch $result {
94a0a42403SMichael Walsh  #     0 {
95a0a42403SMichael Walsh  #       puts stderr "" ; print_error "Invalid username or password.\n"
96a0a42403SMichael Walsh  #       exit_proc 1
97a0a42403SMichael Walsh  #     }
98a0a42403SMichael Walsh  #     1 {
99a0a42403SMichael Walsh  #       dict set state ssh_logged_in 1
100a0a42403SMichael Walsh  #     }
101a0a42403SMichael Walsh  #   }
102a0a42403SMichael Walsh
103a0a42403SMichael Walsh  global spawn_id
104a0a42403SMichael Walsh  global expect_out
105a0a42403SMichael Walsh
106a0a42403SMichael Walsh  # Recognized flags.
107a0a42403SMichael Walsh  set flags [list "-re" "-ex" "-gl"]
108a0a42403SMichael Walsh
109a0a42403SMichael Walsh  # This helps debug efforts by removing leftover, stale entries.
110a0a42403SMichael Walsh  array unset expect_out \[1-9\],string
111a0a42403SMichael Walsh
112a0a42403SMichael Walsh  # Prepare the expect statement.
113a0a42403SMichael Walsh  append cmd_buf "global spawn_id\n"
114a0a42403SMichael Walsh  append cmd_buf "global expect_out\n"
115a0a42403SMichael Walsh  append cmd_buf "expect {\n"
116a0a42403SMichael Walsh  set ix 0
117a0a42403SMichael Walsh  foreach pattern $pattern_list {
118*410b1787SMichael Walsh    # Check to see whether the caller has specified a flag (e.g. "-re", "-ex", etc.) at the beginning of the
119*410b1787SMichael Walsh    # pattern.
120a0a42403SMichael Walsh    set tokens [split $pattern " "]
121a0a42403SMichael Walsh    if { [lsearch $flags [lindex $tokens 0]] != -1 } {
122a0a42403SMichael Walsh      # Caller specified a flag.
123a0a42403SMichael Walsh      set flag [lindex $tokens 0]
124a0a42403SMichael Walsh      # Strip the flag from the pattern.
125a0a42403SMichael Walsh      set pattern [string range $pattern 4 end]
126a0a42403SMichael Walsh    } else {
127a0a42403SMichael Walsh      set flag "-re"
128a0a42403SMichael Walsh    }
129a0a42403SMichael Walsh    append cmd_buf "  ${flag} {$pattern} {set expect_result $ix}\n"
130a0a42403SMichael Walsh    incr ix
131a0a42403SMichael Walsh  }
13219d33f5dSJoy Onyerikwu  if { $fail_on_timeout } {
133a0a42403SMichael Walsh    append cmd_buf "  timeout {handle_timeout \$message}\n"
13419d33f5dSJoy Onyerikwu  } else {
13519d33f5dSJoy Onyerikwu    append cmd_buf "  timeout {set expect_result \[expect_wrap_timeout\]}\n"
13619d33f5dSJoy Onyerikwu  }
137a0a42403SMichael Walsh  append cmd_buf "  eof {handle_eof \$message}\n"
138a0a42403SMichael Walsh  append cmd_buf "}\n"
139a0a42403SMichael Walsh
140a0a42403SMichael Walsh  dprint_timen "Expecting $message."
141a0a42403SMichael Walsh  dprint_issuing "\n${cmd_buf}"
142a0a42403SMichael Walsh  eval ${cmd_buf}
143a0a42403SMichael Walsh
144a0a42403SMichael Walsh  dprintn ; dprint_vars expect_out expect_result
145a0a42403SMichael Walsh
146a0a42403SMichael Walsh  return $expect_result
147a0a42403SMichael Walsh
148a0a42403SMichael Walsh}
149a0a42403SMichael Walsh
150e8899320SMichael Walsh
15119d33f5dSJoy Onyerikwuproc expect_wrap_timeout {} {
15219d33f5dSJoy Onyerikwu
15319d33f5dSJoy Onyerikwu  # Return constant value of 1000.
15419d33f5dSJoy Onyerikwu
15519d33f5dSJoy Onyerikwu  return 1000
15619d33f5dSJoy Onyerikwu
15719d33f5dSJoy Onyerikwu}
15819d33f5dSJoy Onyerikwu
159a0a42403SMichael Walsh
160a0a42403SMichael Walshproc send_wrap {buffer {add_lf 1}} {
161a0a42403SMichael Walsh
162a0a42403SMichael Walsh  # Send the buffer to the spawned process.
163a0a42403SMichael Walsh
164*410b1787SMichael Walsh  # This function offers the following benefits over calling the send command directly:
165*410b1787SMichael Walsh  # - It makes program debug easier.  When the program is run with --debug=1, this function prints useful
166*410b1787SMichael Walsh  #   debug output.
167a0a42403SMichael Walsh
168a0a42403SMichael Walsh  # Description of argument(s):
169*410b1787SMichael Walsh  # buffer                          The string to be sent to the spawned process.
170a0a42403SMichael Walsh  # add_lf                          Send a line feed after sending the buffer.
171a0a42403SMichael Walsh
172a0a42403SMichael Walsh  # Example usage.
173a0a42403SMichael Walsh  # Close the ssh session.
174a0a42403SMichael Walsh  #   send_wrap "~."
175a0a42403SMichael Walsh  #
176a0a42403SMichael Walsh  #   set expect_result [expect_wrap\
177a0a42403SMichael Walsh  #     [list "Connection to $host closed"]\
178a0a42403SMichael Walsh  #     "a connection closed message" 5]
179a0a42403SMichael Walsh
180a0a42403SMichael Walsh  global spawn_id
181a0a42403SMichael Walsh  global expect_out
182a0a42403SMichael Walsh
183a0a42403SMichael Walsh  set cmd_buf "send -- {${buffer}}"
184a0a42403SMichael Walsh  dprint_issuing
185a0a42403SMichael Walsh  eval ${cmd_buf}
186a0a42403SMichael Walsh
187a0a42403SMichael Walsh  if { $add_lf } {
188a0a42403SMichael Walsh    send -- "\n"
189a0a42403SMichael Walsh    set cmd_buf "send -- \"\\n\""
190a0a42403SMichael Walsh    dprint_issuing
191a0a42403SMichael Walsh    eval ${cmd_buf}
192a0a42403SMichael Walsh  }
193a0a42403SMichael Walsh
194a0a42403SMichael Walsh}
19556fd36a4SJoy Onyerikwu
19656fd36a4SJoy Onyerikwu
19756fd36a4SJoy Onyerikwuproc shell_command {command_string {prompt_regex} { quiet {} } \
19856fd36a4SJoy Onyerikwu  { test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
19956fd36a4SJoy Onyerikwu
200*410b1787SMichael Walsh  # Execute the command_string on the shell command line and return a list consisting of 1) the return code
201*410b1787SMichael Walsh  # of the command 2) the stdout/stderr.
20256fd36a4SJoy Onyerikwu
203*410b1787SMichael Walsh  # It is the caller's responsibility to spawn the appropriate process (ssh,telnet) and to get the process
204*410b1787SMichael Walsh  # to a shell command line (by logging in, etc.).
20556fd36a4SJoy Onyerikwu
20656fd36a4SJoy Onyerikwu  # Description of argument(s):
207*410b1787SMichael Walsh  # command_string                  The command string which is to be run on the shell (e.g. "hostname" or
208*410b1787SMichael Walsh  #                                 "grep this that").
209*410b1787SMichael Walsh  # prompt_regex                    A regular expression to match the prompt for current shell to run on (e.g
210*410b1787SMichael Walsh  #                                 "/ #").
211*410b1787SMichael Walsh  # quiet                           Indicates whether this procedure should run the print_issuing() procedure
212*410b1787SMichael Walsh  #                                 which prints "Issuing: <cmd string>" to stdout. The default value is 0.
213*410b1787SMichael Walsh  # test_mode                       If test_mode is set, this procedure will not actually run the command.
214*410b1787SMichael Walsh  #                                 If print_output is set, it will print "(test_mode) Issuing: <cmd string>"
215*410b1787SMichael Walsh  #                                 to stdout.  The default value is 0.
216*410b1787SMichael Walsh  # show_err                        If show_err is set, this procedure will print a standardized error report
217*410b1787SMichael Walsh  #                                 if the shell command returns non-zero.  The default value is 1.
218*410b1787SMichael Walsh  # ignore_err                      If ignore_err is set, this procedure will not fail if the shell command
219*410b1787SMichael Walsh  #                                 fails.  However, if ignore_err is not set, this procedure will exit 1 if
220*410b1787SMichael Walsh  #                                 the shell command fails.  The default value is 1.
221*410b1787SMichael Walsh  # trim_cr_lf                      Trim any trailing carriage return or line feed from the result.
22256fd36a4SJoy Onyerikwu
223*410b1787SMichael Walsh  # Set defaults (this section allows users to pass blank values for certain args).
22456fd36a4SJoy Onyerikwu  set_var_default quiet [get_stack_var quiet 0 2]
22556fd36a4SJoy Onyerikwu  set_var_default test_mode 0
22656fd36a4SJoy Onyerikwu  set_var_default show_err 1
22756fd36a4SJoy Onyerikwu  set_var_default ignore_err 0
22856fd36a4SJoy Onyerikwu  set_var_default acceptable_shell_rcs 0
22956fd36a4SJoy Onyerikwu
23056fd36a4SJoy Onyerikwu  global spawn_id
23156fd36a4SJoy Onyerikwu  global expect_out
23256fd36a4SJoy Onyerikwu
23356fd36a4SJoy Onyerikwu  qprintn ; qprint_issuing ${command_string} ${test_mode}
23456fd36a4SJoy Onyerikwu
23556fd36a4SJoy Onyerikwu  if { $test_mode } {
23656fd36a4SJoy Onyerikwu    return [list 0 ""]
23756fd36a4SJoy Onyerikwu  }
23856fd36a4SJoy Onyerikwu
23956fd36a4SJoy Onyerikwu  send_wrap "${command_string}"
24056fd36a4SJoy Onyerikwu
24156fd36a4SJoy Onyerikwu  set expect_result [expect_wrap\
24256fd36a4SJoy Onyerikwu    [list "-ex $command_string"]\
24356fd36a4SJoy Onyerikwu    "the echoed command" 5]
24456fd36a4SJoy Onyerikwu  set expect_result [expect_wrap\
24556fd36a4SJoy Onyerikwu    [list {[\n\r]{1,2}}]\
24656fd36a4SJoy Onyerikwu    "one or two line feeds" 5]
24756fd36a4SJoy Onyerikwu  # Note the non-greedy specification in the regex below (the "?").
24856fd36a4SJoy Onyerikwu  set expect_result [expect_wrap\
24956fd36a4SJoy Onyerikwu    [list "(.*?)$prompt_regex"]\
25056fd36a4SJoy Onyerikwu    "command output plus prompt" -1]
25156fd36a4SJoy Onyerikwu  # The command's stdout/stderr should be captured as match #1.
25256fd36a4SJoy Onyerikwu  set out_buf $expect_out(1,string)
25356fd36a4SJoy Onyerikwu
25456fd36a4SJoy Onyerikwu  if { $trim_cr_lf } {
25556fd36a4SJoy Onyerikwu    set out_buf [ string trimright $out_buf "\r\n" ]
25656fd36a4SJoy Onyerikwu  }
25756fd36a4SJoy Onyerikwu
25856fd36a4SJoy Onyerikwu  # Get rc via recursive call to this function.
25956fd36a4SJoy Onyerikwu  set rc 0
26056fd36a4SJoy Onyerikwu  set proc_name [get_stack_proc_name]
26156fd36a4SJoy Onyerikwu  set calling_proc_name [get_stack_proc_name -2]
26256fd36a4SJoy Onyerikwu  if { $calling_proc_name != $proc_name } {
26356fd36a4SJoy Onyerikwu    set sub_result [shell_command {echo ${?}} $prompt_regex 1]
26456fd36a4SJoy Onyerikwu    dprintn ; dprint_list sub_result
26556fd36a4SJoy Onyerikwu    set rc [lindex $sub_result 1]
26656fd36a4SJoy Onyerikwu  }
26756fd36a4SJoy Onyerikwu
26856fd36a4SJoy Onyerikwu  if { $rc != 0 } {
26956fd36a4SJoy Onyerikwu    if { $show_err } {
27056fd36a4SJoy Onyerikwu      puts stderr "" ; print_error_report "The prior shell command failed.\n"
27156fd36a4SJoy Onyerikwu    }
27256fd36a4SJoy Onyerikwu    if { ! $ignore_err } {
27356fd36a4SJoy Onyerikwu      if { [info procs "exit_proc"] != "" } {
27456fd36a4SJoy Onyerikwu        exit_proc 1
27556fd36a4SJoy Onyerikwu      }
27656fd36a4SJoy Onyerikwu    }
27756fd36a4SJoy Onyerikwu  }
27856fd36a4SJoy Onyerikwu
27956fd36a4SJoy Onyerikwu  return [list $rc $out_buf]
28056fd36a4SJoy Onyerikwu
28156fd36a4SJoy Onyerikwu}
282