Added utilities file for SOL

Change-Id: I15878152404846db4058603f3ab5a246c704a6b0
Signed-off-by: Joy Onyerikwu <onyekachukwu.joy.onyerikwu@ibm.com>
diff --git a/bin/sol_utils.tcl b/bin/sol_utils.tcl
new file mode 100755
index 0000000..e5be1b6
--- /dev/null
+++ b/bin/sol_utils.tcl
@@ -0,0 +1,480 @@
+#!/bin/bash
+#\
+exec expect "$0" -- ${1+"$@"}
+
+# This file contains utilities for working with Serial over Lan (SOL).
+
+# Example use case:
+# sol_utils.tcl --os_host=ip --os_password=password --os_username=username
+# --openbmc_host=ip --openbmc_password=password --openbmc_username=username
+# --proc_name=boot_to_petitboot
+
+source [exec bash -c "which source.tcl"]
+my_source [list print.tcl opt.tcl valid.tcl call_stack.tcl tools.exp]
+
+longoptions openbmc_host: openbmc_username:=root openbmc_password:=0penBmc\
+  os_host: os_username:=root os_password: proc_name:=boot_to_petitboot\
+  test_mode:=0 quiet:=0 debug:=0
+pos_parms
+
+set valid_proc_name [list os_login boot_to_petitboot]
+
+# Create help dictionary for call to gen_print_help.
+set help_dict [dict create\
+  ${program_name} [list "${program_name} is an SOL utilities program that\
+    will run the user's choice of utilities.  See the \"proc_name\" parm below\
+    for details."]\
+  openbmc_host [list "The OpenBMC host name or IP address." "host"]\
+  openbmc_username [list "The OpenBMC username." "username"]\
+  openbmc_password [list "The OpenBMC password." "password"]\
+  os_host [list "The OS host name or IP address." "host"]\
+  os_username [list "The OS username." "username"]\
+  os_password [list "The OS password." "password"]\
+  proc_name [list "The proc_name you'd like to run.  Valid values are as\
+    follows: [regsub -all {\s+} $valid_proc_name {, }]."]\
+]
+
+
+# Setup state dictionary.
+set state [dict create\
+  ssh_logged_in 0\
+  os_login_prompt 0\
+  os_logged_in 0\
+  petitboot_screen 0\
+]
+
+
+proc help {} {
+
+  gen_print_help
+
+}
+
+
+proc exit_proc { {ret_code 0} } {
+
+  # Execute whenever the program ends normally or with the signals that we
+  # catch (i.e. TERM, INT).
+
+  dprintn ; dprint_executing
+  dprint_var ret_code
+
+  set cmd_buf os_logoff
+  qprintn ; qprint_issuing
+  eval ${cmd_buf}
+
+  set cmd_buf sol_logoff
+  qprintn ; qprint_issuing
+  eval ${cmd_buf}
+
+  qprint_pgm_footer
+
+  exit $ret_code
+
+}
+
+
+proc validate_parms {} {
+
+  trap { exit_proc } [list SIGTERM SIGINT]
+
+  valid_value openbmc_host
+  valid_value openbmc_username
+  valid_value openbmc_password
+  valid_value os_host
+  valid_value os_username
+  valid_value os_password
+  global valid_proc_name
+  valid_value proc_name {} $valid_proc_name
+
+}
+
+
+proc sol_login {} {
+
+  # Login to the SOL console.
+
+  dprintn ; dprint_executing
+
+  global spawn_id
+  global expect_out
+  global state
+  global openbmc_host openbmc_username openbmc_password
+  global cr_lf_regex
+  global ssh_password_prompt
+
+  set cmd_buf "spawn -nottycopy ssh -p 2200 $openbmc_username@$openbmc_host"
+  qprint_issuing
+  eval $cmd_buf
+
+  append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
+  append bad_host_regex " Name or service not known"
+  set expect_result [expect_wrap\
+    [list $bad_host_regex $ssh_password_prompt]\
+    "an SOL password prompt" 5]
+
+  if { $expect_result == 0 } {
+    puts stderr ""
+    print_error "Invalid openbmc_host value.\n"
+    exit_proc 1
+  }
+
+  send_wrap "${openbmc_password}"
+
+  append bad_user_pw_regex "Permission denied, please try again\."
+  append bad_user_pw_regex "${cr_lf_regex}${ssh_password_prompt}"
+  set expect_result [expect_wrap\
+    [list $bad_user_pw_regex "sh: xauth: command not found"]\
+    "an SOL prompt" 10]
+
+  switch $expect_result {
+    0 {
+      puts stderr "" ; print_error "Invalid OpenBmc username or password.\n"
+      exit_proc 1
+    }
+    1 {
+      # Currently, this string always appears but that is not necessarily
+      # guaranteed.
+      dict set state ssh_logged_in 1
+    }
+  }
+
+  if { [dict get $state ssh_logged_in] } {
+    qprintn ; qprint_timen "Logged into SOL."
+    dprintn ; dprint_dict state
+    return
+  }
+
+  # If we didn't get a hit on the "sh: xauth: command not found", then we just
+  # need to see a linefeed.
+  set expect_result [expect_wrap [list ${cr_lf_regex}] "an SOL prompt" 5]
+
+  dict set state ssh_logged_in 1
+  qprintn ; qprint_timen "Logged into SOL."
+  dprintn ; dprint_dict state
+
+}
+
+
+proc sol_logoff {} {
+
+  # Logoff from the SOL console.
+
+  dprintn ; dprint_executing
+
+  global spawn_id
+  global expect_out
+  global state
+  global openbmc_host
+
+  if { ! [dict get $state ssh_logged_in] } {
+    qprintn ; qprint_timen "No SOL logoff required."
+    return
+  }
+
+  send_wrap "~."
+
+  set expect_result [expect_wrap\
+    [list "Connection to $openbmc_host closed"]\
+    "a connection closed message" 5]
+
+  dict set state ssh_logged_in 0
+  qprintn ; qprint_timen "Logged off SOL."
+  dprintn ; dprint_dict state
+
+}
+
+
+proc get_post_ssh_login_state {} {
+
+  # Get the initial state following sol_login.
+
+  # The following state global dictionary variable is set by this procedure.
+
+  dprintn ; dprint_executing
+
+  global spawn_id
+  global expect_out
+  global state
+  global os_login_prompt_regex
+  global os_prompt_regex
+  global petitboot_screen_regex
+
+  if { ! [dict get $state ssh_logged_in] } {
+    puts stderr ""
+    append message "Programmer error - [get_stack_proc_name] must only be"
+    append message " called after sol_login has been called."
+    print_error_report $message
+    exit_proc 1
+  }
+
+  # The first thing one must do after signing into ssh -p 2200 is hit enter to
+  # see where things stand.
+  send_wrap ""
+  set expect_result [expect_wrap\
+    [list $os_login_prompt_regex $os_prompt_regex $petitboot_screen_regex]\
+    "any indication of status" 5]
+
+  switch $expect_result {
+    0 {
+      dict set state os_login_prompt 1
+    }
+    1 {
+      dict set state os_logged_in 1
+    }
+    2 {
+      dict set state petitboot_screen 1
+    }
+  }
+
+  dprintn ; dprint_dict state
+
+}
+
+
+proc os_login {} {
+
+  # Login to the OS
+
+  dprintn ; dprint_executing
+
+  global spawn_id
+  global expect_out
+  global state
+  global openbmc_host os_username os_password
+  global os_password_prompt
+  global os_prompt_regex
+
+  if { [dict get $state os_logged_in] } {
+    printn ; print_timen "We are already logged in to the OS."
+    return
+  }
+
+  send_wrap "${os_username}"
+
+  append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
+  append bad_host_regex " Name or service not known"
+  set expect_result [expect_wrap\
+    [list $os_password_prompt]\
+    "an OS password prompt" 5]
+
+  send_wrap "${os_password}"
+  set expect_result [expect_wrap\
+    [list "Login incorrect" "$os_prompt_regex"]\
+    "an OS prompt" 10]
+  switch $expect_result {
+    0 {
+      puts stderr "" ; print_error "Invalid OS username or password.\n"
+      exit_proc 1
+    }
+  }
+
+  dict set state os_logged_in 1
+  dict set state os_login_prompt 0
+  qprintn ; qprint_timen "Logged into OS."
+  dprintn ; dprint_dict state
+
+}
+
+
+proc os_logoff {} {
+
+  # Logoff from the SOL console.
+
+  dprintn ; dprint_executing
+
+  global spawn_id
+  global expect_out
+  global state
+  global os_login_prompt_regex
+
+  if { ! [dict get $state os_logged_in] } {
+    qprintn ; qprint_timen "No OS logoff required."
+    return
+  }
+
+  send_wrap "exit"
+  set expect_result [expect_wrap\
+    [list $os_login_prompt_regex]\
+    "an OS prompt" 5]
+
+  dict set state os_logged_in 0
+  qprintn ; qprint_timen "Logged off OS."
+  dprintn ; dprint_dict state
+
+}
+
+
+proc os_command {command_string { quiet {} } { test_mode {} } \
+  { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
+
+  # Execute the command_string on the OS command line and return a list
+  # consisting of 1) the return code of the command 2) the stdout/
+  # stderr.
+
+  # It is the caller's responsibility to make sure we are logged into the OS.
+
+  # Description of argument(s):
+  # command_string  The command string which is to be run on the OS (e.g.
+  #                 "hostname" or "grep this that").
+  # quiet           Indicates whether this procedure should run the
+  #                 print_issuing() procedure which prints "Issuing:
+  #                 <cmd string>" to stdout. The default value is 0.
+  # test_mode       If test_mode is set, this procedure will not actually run
+  #                 the command.  If print_output is set, it will print
+  #                 "(test_mode) Issuing: <cmd string>" to stdout.  The default
+  #                 value is 0.
+  # show_err        If show_err is set, this procedure will print a
+  #                 standardized error report if the shell command returns non-
+  #                 zero.  The default value is 1.
+  # ignore_err      If ignore_err is set, this procedure will not fail if the
+  #                 shell command fails.  However, if ignore_err is not set,
+  #                 this procedure will exit 1 if the shell command fails.  The
+  #                 default value is 1.
+  # trim_cr_lf      Trim any trailing carriage return or line feed from the
+  #                 result.
+
+  # Set defaults (this section allows users to pass blank values for certain
+  # args)
+  set_var_default quiet [get_stack_var quiet 0 2]
+  set_var_default test_mode 0
+  set_var_default show_err 1
+  set_var_default ignore_err 0
+  set_var_default acceptable_shell_rcs 0
+
+  global spawn_id
+  global expect_out
+  global os_prompt_regex
+
+  qprintn ; qprint_issuing ${command_string} ${test_mode}
+
+  if { $test_mode } {
+    return [list 0 ""]
+  }
+
+  send_wrap "${command_string}"
+
+  set expect_result [expect_wrap\
+    [list "-ex $command_string"]\
+    "the echoed command" 5]
+  set expect_result [expect_wrap\
+    [list {[\n\r]{1,2}}]\
+    "one or two line feeds" 5]
+  # Note the non-greedy specification in the regex below (the "?").
+  set expect_result [expect_wrap\
+    [list "(.*?)$os_prompt_regex"]\
+    "command output plus prompt" -1]
+
+  # The command's stdout/stderr should be captured as match #1.
+  set out_buf $expect_out(1,string)
+
+  if { $trim_cr_lf } {
+    set out_buf [ string trimright $out_buf "\r\n" ]
+  }
+
+  # Get rc via recursive call to this function.
+  set rc 0
+  set proc_name [get_stack_proc_name]
+  set calling_proc_name [get_stack_proc_name -2]
+  if { $calling_proc_name != $proc_name } {
+    set sub_result [os_command {echo ${?}} 1]
+    dprintn ; dprint_list sub_result
+    set rc [lindex $sub_result 1]
+  }
+
+  if { $rc != 0 } {
+    if { $show_err } {
+      puts stderr "" ; print_error_report "The prior OS command failed.\n"
+    }
+    if { ! $ignore_err } {
+      if { [info procs "exit_proc"] != "" } {
+        exit_proc 1
+      }
+    }
+  }
+
+  return [list $rc $out_buf]
+
+}
+
+
+proc boot_to_petitboot {} {
+
+  # Boot the machine until the petitboot screen is reached.
+
+  dprintn ; dprint_executing
+
+  global spawn_id
+  global expect_out
+  global state
+  global os_prompt_regex
+  global petitboot_screen_regex
+
+  if { [dict get $state petitboot_screen] } {
+    qprintn ; qprint_timen "We are already at petiboot."
+    return
+  }
+
+  if { [dict get $state os_login_prompt] } {
+    set cmd_buf os_login
+    qprintn ; qprint_issuing
+    eval ${cmd_buf}
+  }
+
+  # Turn off autoboot.
+  set cmd_result [os_command "nvram --update-config auto-boot?=false"]
+  set cmd_result [os_command\
+    "nvram --print-config | egrep 'auto\\-boot\\?=false'"]
+
+  # Reboot and wait for petitboot.
+  send_wrap "reboot"
+
+  # Once we've started a reboot, we are no longer logged into OS.
+  dict set state os_logged_in 0
+  dict set state os_login_prompt 0
+
+  set expect_result [expect_wrap\
+    [list $petitboot_screen_regex]\
+    "the petitboot screen" 900]
+  set expect_result [expect_wrap\
+    [list "Exit to shell"]\
+    "the 'Exit to shell' screen" 10]
+  dict set state petitboot_screen 1
+
+  qprintn ; qprint_timen "Arrived at petitboot screen."
+  dprintn ; dprint_dict state
+
+}
+
+
+# Main
+
+  gen_get_options $argv
+
+  validate_parms
+
+  qprint_pgm_header
+
+  # Global variables for current prompts of the SOL console.
+  set ssh_password_prompt ".* password: "
+  set os_login_prompt_regex "login: "
+  set os_password_prompt "Password: "
+  set petitboot_screen_regex "Petitboot"
+  set cr_lf_regex "\[\n\r\]"
+  set os_prompt_regex "(\\\[${os_username}@\[^ \]+ ~\\\]# )"
+
+  dprintn ; dprint_dict state
+
+  set cmd_buf sol_login
+  qprint_issuing
+  eval ${cmd_buf}
+
+  set cmd_buf get_post_ssh_login_state
+  qprintn ; qprint_issuing
+  eval ${cmd_buf}
+
+  set cmd_buf ${proc_name}
+  qprintn ; qprint_issuing
+  eval ${cmd_buf}
+
+  exit_proc