blob: e5be1b68f3f32380514607ac353d2ef0d9062fce [file] [log] [blame]
Joy Onyerikwu223f1752018-02-21 18:08:02 -06001#!/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