Joy Onyerikwu | 223f175 | 2018-02-21 18:08:02 -0600 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | #\ |
| 3 | exec 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 | |
| 12 | source [exec bash -c "which source.tcl"] |
| 13 | my_source [list print.tcl opt.tcl valid.tcl call_stack.tcl tools.exp] |
| 14 | |
| 15 | longoptions 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 |
| 18 | pos_parms |
| 19 | |
| 20 | set valid_proc_name [list os_login boot_to_petitboot] |
| 21 | |
| 22 | # Create help dictionary for call to gen_print_help. |
| 23 | set 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. |
| 39 | set 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 | |
| 47 | proc help {} { |
| 48 | |
| 49 | gen_print_help |
| 50 | |
| 51 | } |
| 52 | |
| 53 | |
| 54 | proc 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 | |
| 77 | proc 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 | |
| 93 | proc 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 | |
| 159 | proc 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 | |
| 188 | proc 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 | |
| 235 | proc 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 | |
| 280 | proc 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 | |
| 308 | proc 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 | |
| 401 | proc 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 |