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