1#!/bin/bash
2#\
3exec expect "$0" -- ${1+"$@"}
4
5# This file contains utilities for working with Serial over Lan (SOL).
6
7# Example use case:
8# sol_utils.tcl --os_host=ip --os_password=password --os_username=username
9# --openbmc_host=ip --openbmc_password=password --openbmc_username=username
10# --proc_name=boot_to_petitboot
11
12source [exec bash -c "which source.tcl"]
13my_source [list print.tcl opt.tcl valid.tcl call_stack.tcl tools.exp]
14
15longoptions openbmc_host: openbmc_username:=root openbmc_password:=0penBmc\
16  os_host: os_username:=root os_password: proc_name:=boot_to_petitboot\
17  test_mode:=0 quiet:=0 debug:=0
18pos_parms
19
20set valid_proc_name [list os_login boot_to_petitboot]
21
22# Create help dictionary for call to gen_print_help.
23set help_dict [dict create\
24  ${program_name} [list "${program_name} is an SOL utilities program that\
25    will run the user's choice of utilities.  See the \"proc_name\" parm below\
26    for details."]\
27  openbmc_host [list "The OpenBMC host name or IP address." "host"]\
28  openbmc_username [list "The OpenBMC username." "username"]\
29  openbmc_password [list "The OpenBMC password." "password"]\
30  os_host [list "The OS host name or IP address." "host"]\
31  os_username [list "The OS username." "username"]\
32  os_password [list "The OS password." "password"]\
33  proc_name [list "The proc_name you'd like to run.  Valid values are as\
34    follows: [regsub -all {\s+} $valid_proc_name {, }]."]\
35]
36
37
38# Setup state dictionary.
39set state [dict create\
40  ssh_logged_in 0\
41  os_login_prompt 0\
42  os_logged_in 0\
43  petitboot_screen 0\
44]
45
46
47proc help {} {
48
49  gen_print_help
50
51}
52
53
54proc exit_proc { {ret_code 0} } {
55
56  # Execute whenever the program ends normally or with the signals that we
57  # catch (i.e. TERM, INT).
58
59  dprintn ; dprint_executing
60  dprint_var ret_code
61
62  set cmd_buf os_logoff
63  qprintn ; qprint_issuing
64  eval ${cmd_buf}
65
66  set cmd_buf sol_logoff
67  qprintn ; qprint_issuing
68  eval ${cmd_buf}
69
70  qprint_pgm_footer
71
72  exit $ret_code
73
74}
75
76
77proc validate_parms {} {
78
79  trap { exit_proc } [list SIGTERM SIGINT]
80
81  valid_value openbmc_host
82  valid_value openbmc_username
83  valid_value openbmc_password
84  valid_value os_host
85  valid_value os_username
86  valid_value os_password
87  global valid_proc_name
88  valid_value proc_name {} $valid_proc_name
89
90}
91
92
93proc sol_login {} {
94
95  # Login to the SOL console.
96
97  dprintn ; dprint_executing
98
99  global spawn_id
100  global expect_out
101  global state
102  global openbmc_host openbmc_username openbmc_password
103  global cr_lf_regex
104  global ssh_password_prompt
105
106  set cmd_buf "spawn -nottycopy ssh -p 2200 $openbmc_username@$openbmc_host"
107  qprint_issuing
108  eval $cmd_buf
109
110  append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
111  append bad_host_regex " Name or service not known"
112  set expect_result [expect_wrap\
113    [list $bad_host_regex $ssh_password_prompt]\
114    "an SOL password prompt" 5]
115
116  if { $expect_result == 0 } {
117    puts stderr ""
118    print_error "Invalid openbmc_host value.\n"
119    exit_proc 1
120  }
121
122  send_wrap "${openbmc_password}"
123
124  append bad_user_pw_regex "Permission denied, please try again\."
125  append bad_user_pw_regex "${cr_lf_regex}${ssh_password_prompt}"
126  set expect_result [expect_wrap\
127    [list $bad_user_pw_regex "sh: xauth: command not found"]\
128    "an SOL prompt" 10]
129
130  switch $expect_result {
131    0 {
132      puts stderr "" ; print_error "Invalid OpenBmc username or password.\n"
133      exit_proc 1
134    }
135    1 {
136      # Currently, this string always appears but that is not necessarily
137      # guaranteed.
138      dict set state ssh_logged_in 1
139    }
140  }
141
142  if { [dict get $state ssh_logged_in] } {
143    qprintn ; qprint_timen "Logged into SOL."
144    dprintn ; dprint_dict state
145    return
146  }
147
148  # If we didn't get a hit on the "sh: xauth: command not found", then we just
149  # need to see a linefeed.
150  set expect_result [expect_wrap [list ${cr_lf_regex}] "an SOL prompt" 5]
151
152  dict set state ssh_logged_in 1
153  qprintn ; qprint_timen "Logged into SOL."
154  dprintn ; dprint_dict state
155
156}
157
158
159proc sol_logoff {} {
160
161  # Logoff from the SOL console.
162
163  dprintn ; dprint_executing
164
165  global spawn_id
166  global expect_out
167  global state
168  global openbmc_host
169
170  if { ! [dict get $state ssh_logged_in] } {
171    qprintn ; qprint_timen "No SOL logoff required."
172    return
173  }
174
175  send_wrap "~."
176
177  set expect_result [expect_wrap\
178    [list "Connection to $openbmc_host closed"]\
179    "a connection closed message" 5]
180
181  dict set state ssh_logged_in 0
182  qprintn ; qprint_timen "Logged off SOL."
183  dprintn ; dprint_dict state
184
185}
186
187
188proc get_post_ssh_login_state {} {
189
190  # Get the initial state following sol_login.
191
192  # The following state global dictionary variable is set by this procedure.
193
194  dprintn ; dprint_executing
195
196  global spawn_id
197  global expect_out
198  global state
199  global os_login_prompt_regex
200  global os_prompt_regex
201  global petitboot_screen_regex
202
203  if { ! [dict get $state ssh_logged_in] } {
204    puts stderr ""
205    append message "Programmer error - [get_stack_proc_name] must only be"
206    append message " called after sol_login has been called."
207    print_error_report $message
208    exit_proc 1
209  }
210
211  # The first thing one must do after signing into ssh -p 2200 is hit enter to
212  # see where things stand.
213  send_wrap ""
214  set expect_result [expect_wrap\
215    [list $os_login_prompt_regex $os_prompt_regex $petitboot_screen_regex]\
216    "any indication of status" 5]
217
218  switch $expect_result {
219    0 {
220      dict set state os_login_prompt 1
221    }
222    1 {
223      dict set state os_logged_in 1
224    }
225    2 {
226      dict set state petitboot_screen 1
227    }
228  }
229
230  dprintn ; dprint_dict state
231
232}
233
234
235proc os_login {} {
236
237  # Login to the OS
238
239  dprintn ; dprint_executing
240
241  global spawn_id
242  global expect_out
243  global state
244  global openbmc_host os_username os_password
245  global os_password_prompt
246  global os_prompt_regex
247
248  if { [dict get $state os_logged_in] } {
249    printn ; print_timen "We are already logged in to the OS."
250    return
251  }
252
253  send_wrap "${os_username}"
254
255  append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
256  append bad_host_regex " Name or service not known"
257  set expect_result [expect_wrap\
258    [list $os_password_prompt]\
259    "an OS password prompt" 5]
260
261  send_wrap "${os_password}"
262  set expect_result [expect_wrap\
263    [list "Login incorrect" "$os_prompt_regex"]\
264    "an OS prompt" 10]
265  switch $expect_result {
266    0 {
267      puts stderr "" ; print_error "Invalid OS username or password.\n"
268      exit_proc 1
269    }
270  }
271
272  dict set state os_logged_in 1
273  dict set state os_login_prompt 0
274  qprintn ; qprint_timen "Logged into OS."
275  dprintn ; dprint_dict state
276
277}
278
279
280proc os_logoff {} {
281
282  # Logoff from the SOL console.
283
284  dprintn ; dprint_executing
285
286  global spawn_id
287  global expect_out
288  global state
289  global os_login_prompt_regex
290
291  if { ! [dict get $state os_logged_in] } {
292    qprintn ; qprint_timen "No OS logoff required."
293    return
294  }
295
296  send_wrap "exit"
297  set expect_result [expect_wrap\
298    [list $os_login_prompt_regex]\
299    "an OS prompt" 5]
300
301  dict set state os_logged_in 0
302  qprintn ; qprint_timen "Logged off OS."
303  dprintn ; dprint_dict state
304
305}
306
307
308proc os_command {command_string { quiet {} } { test_mode {} } \
309  { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
310
311  # Execute the command_string on the OS command line and return a list
312  # consisting of 1) the return code of the command 2) the stdout/
313  # stderr.
314
315  # It is the caller's responsibility to make sure we are logged into the OS.
316
317  # Description of argument(s):
318  # command_string  The command string which is to be run on the OS (e.g.
319  #                 "hostname" or "grep this that").
320  # quiet           Indicates whether this procedure should run the
321  #                 print_issuing() procedure which prints "Issuing:
322  #                 <cmd string>" to stdout. The default value is 0.
323  # test_mode       If test_mode is set, this procedure will not actually run
324  #                 the command.  If print_output is set, it will print
325  #                 "(test_mode) Issuing: <cmd string>" to stdout.  The default
326  #                 value is 0.
327  # show_err        If show_err is set, this procedure will print a
328  #                 standardized error report if the shell command returns non-
329  #                 zero.  The default value is 1.
330  # ignore_err      If ignore_err is set, this procedure will not fail if the
331  #                 shell command fails.  However, if ignore_err is not set,
332  #                 this procedure will exit 1 if the shell command fails.  The
333  #                 default value is 1.
334  # trim_cr_lf      Trim any trailing carriage return or line feed from the
335  #                 result.
336
337  # Set defaults (this section allows users to pass blank values for certain
338  # args)
339  set_var_default quiet [get_stack_var quiet 0 2]
340  set_var_default test_mode 0
341  set_var_default show_err 1
342  set_var_default ignore_err 0
343  set_var_default acceptable_shell_rcs 0
344
345  global spawn_id
346  global expect_out
347  global os_prompt_regex
348
349  qprintn ; qprint_issuing ${command_string} ${test_mode}
350
351  if { $test_mode } {
352    return [list 0 ""]
353  }
354
355  send_wrap "${command_string}"
356
357  set expect_result [expect_wrap\
358    [list "-ex $command_string"]\
359    "the echoed command" 5]
360  set expect_result [expect_wrap\
361    [list {[\n\r]{1,2}}]\
362    "one or two line feeds" 5]
363  # Note the non-greedy specification in the regex below (the "?").
364  set expect_result [expect_wrap\
365    [list "(.*?)$os_prompt_regex"]\
366    "command output plus prompt" -1]
367
368  # The command's stdout/stderr should be captured as match #1.
369  set out_buf $expect_out(1,string)
370
371  if { $trim_cr_lf } {
372    set out_buf [ string trimright $out_buf "\r\n" ]
373  }
374
375  # Get rc via recursive call to this function.
376  set rc 0
377  set proc_name [get_stack_proc_name]
378  set calling_proc_name [get_stack_proc_name -2]
379  if { $calling_proc_name != $proc_name } {
380    set sub_result [os_command {echo ${?}} 1]
381    dprintn ; dprint_list sub_result
382    set rc [lindex $sub_result 1]
383  }
384
385  if { $rc != 0 } {
386    if { $show_err } {
387      puts stderr "" ; print_error_report "The prior OS command failed.\n"
388    }
389    if { ! $ignore_err } {
390      if { [info procs "exit_proc"] != "" } {
391        exit_proc 1
392      }
393    }
394  }
395
396  return [list $rc $out_buf]
397
398}
399
400
401proc boot_to_petitboot {} {
402
403  # Boot the machine until the petitboot screen is reached.
404
405  dprintn ; dprint_executing
406
407  global spawn_id
408  global expect_out
409  global state
410  global os_prompt_regex
411  global petitboot_screen_regex
412
413  if { [dict get $state petitboot_screen] } {
414    qprintn ; qprint_timen "We are already at petiboot."
415    return
416  }
417
418  if { [dict get $state os_login_prompt] } {
419    set cmd_buf os_login
420    qprintn ; qprint_issuing
421    eval ${cmd_buf}
422  }
423
424  # Turn off autoboot.
425  set cmd_result [os_command "nvram --update-config auto-boot?=false"]
426  set cmd_result [os_command\
427    "nvram --print-config | egrep 'auto\\-boot\\?=false'"]
428
429  # Reboot and wait for petitboot.
430  send_wrap "reboot"
431
432  # Once we've started a reboot, we are no longer logged into OS.
433  dict set state os_logged_in 0
434  dict set state os_login_prompt 0
435
436  set expect_result [expect_wrap\
437    [list $petitboot_screen_regex]\
438    "the petitboot screen" 900]
439  set expect_result [expect_wrap\
440    [list "Exit to shell"]\
441    "the 'Exit to shell' screen" 10]
442  dict set state petitboot_screen 1
443
444  qprintn ; qprint_timen "Arrived at petitboot screen."
445  dprintn ; dprint_dict state
446
447}
448
449
450# Main
451
452  gen_get_options $argv
453
454  validate_parms
455
456  qprint_pgm_header
457
458  # Global variables for current prompts of the SOL console.
459  set ssh_password_prompt ".* password: "
460  set os_login_prompt_regex "login: "
461  set os_password_prompt "Password: "
462  set petitboot_screen_regex "Petitboot"
463  set cr_lf_regex "\[\n\r\]"
464  set os_prompt_regex "(\\\[${os_username}@\[^ \]+ ~\\\]# )"
465
466  dprintn ; dprint_dict state
467
468  set cmd_buf sol_login
469  qprint_issuing
470  eval ${cmd_buf}
471
472  set cmd_buf get_post_ssh_login_state
473  qprintn ; qprint_issuing
474  eval ${cmd_buf}
475
476  set cmd_buf ${proc_name}
477  qprintn ; qprint_issuing
478  eval ${cmd_buf}
479
480  exit_proc
481