1#!/bin/bash -e 2 3set -euo pipefail 4 5OPTS="bmcstate,bootprogress,chassiskill,chassisoff,chassison,chassisstate,hoststate,\ 6osstate,power,poweroff,poweron,state,status,hostrebootoff,hostrebooton,recoveryoff,recoveryon,\ 7bmcrebootoff, bmcrebooton, listbootblock listlogs showlog deletelogs, stopofftargets" 8 9USAGE="Usage: obmcutil [-h] [--wait] [--verbose] [--id=<INSTANCE_ID>] 10{$OPTS}" 11 12INTERFACE_ROOT=xyz.openbmc_project 13STATE_INTERFACE=$INTERFACE_ROOT.State 14CONTROL_INTERFACE=$INTERFACE_ROOT.Control 15 16OBJECT_ROOT=/xyz/openbmc_project 17STATE_OBJECT=$OBJECT_ROOT/state 18CONTROL_OBJECT=$OBJECT_ROOT/control 19 20HOST_TIMEOUT_TARGET=obmc-host-timeout@0.target 21HOST_CRASH_TARGET=obmc-host-crash@0.target 22 23## NOTE: The following global variables are used only in the run_timeout cmd. 24## By declaring these globally instead of passing them through the 25## intermediary functions, which may not be "best practice", the readability 26## and cleanliness of the code should at least be increased. 27 28# The command passed in to be executed (e.g. poweron/off, status, etc.) 29# This will be be used in some instances of error reporting 30G_ORIG_CMD= 31# The state an interface should be in after executing the requested command. 32G_REQUESTED_STATE= 33# The query to run during a poweron/off or chassison/off to check that 34# the requested state (G_REQUESTED_STATE) of the interface has been reached. 35G_QUERY= 36# Wait the set period of time for state transitions to be successful before 37# continuing on with the program or reporting an error if timeout reached. 38G_WAIT= 39# Print the journal to the console 40G_VERBOSE= 41# Instance id, default 0 42G_INSTANCE_ID="0" 43 44function print_help() 45{ 46 echo "$USAGE" 47 echo "" 48 echo "positional arguments:" 49 echo " {$OPTS}" 50 echo "" 51 echo "Examples:" 52 echo "" 53 echo "obmcutil hostrebootoff Disable auto reboot of Host from Quiesce state" 54 echo "obmcutil hostrebootoffonetime Disable auto reboot of Host from" 55 echo " Quiesce state for a single boot" 56 echo "obmcutil hostrebooton Enable auto reboot of Host from Quiesce state" 57 echo "" 58 echo "obmcutil bmcrebootoff Disable reboot of BMC" 59 echo "obmcutil bmcrebooton Enable reboot of BMC" 60 echo "" 61 echo "obmcutil recoveryoff Disable handling boot watchdog timeout and host crash" 62 echo " Also, disable BMC and Host auto reboots" 63 echo "" 64 echo "obmcutil recoveryon Enable handling boot watchdog timeout and host crash" 65 echo " Also, enable BMC and Host auto reboots" 66 echo "" 67 echo "obmcutil listbootblock Check for and list any errors blocking the boot" 68 echo " of the system" 69 echo "" 70 echo "obmcutil listlogs List all phosphor-logging entries on the" 71 echo " system" 72 echo "" 73 echo "obmcutil showlog <log> Display details of input log. Format of <log>" 74 echo " should match listlogs output" 75 echo "" 76 echo "obmcutil deletelogs Delete all phosphor-logging entries from" 77 echo " system" 78 echo "obmcutil stopofftargets Manually stop all obmc targets in power off" 79 echo " path" 80 echo "" 81 echo "optional arguments (must precede the positional options above):" 82 echo " -h, --help show this help message and exit" 83 echo " -w, --wait block until state transition succeeds or fails" 84 echo " -v, --verbose print the journal to stdout if --wait is supplied" 85 echo " -i, -id instance id, default 0" 86 exit 0 87} 88 89function run_timeout() 90{ 91 local timeout="$1"; shift 92 local cmd="$*" 93 local verbose_child= 94 95 if [ -n "$G_VERBOSE" ]; then 96 journalctl -f & 97 verbose_child=$! 98 fi 99 100 $cmd 101 102 # Run a background query for the transition to the expected state 103 # This will be killed if the transition doesn't succeed within 104 # a timeout period. 105 ( 106 while ! grep -q "$G_REQUESTED_STATE" <<< "$(handle_cmd "$G_QUERY")" ; do 107 sleep 1 108 done 109 ) & 110 wait_child=$! 111 112 # Could be bad if process is killed before 'timeout' occurs if 113 # transition doesn't succeed. 114 trap -- "" SIGTERM 115 116 # Workaround for lack of 'timeout' command. 117 ( 118 sleep "$timeout" 119 kill $wait_child 120 ) > /dev/null 2>&1 & 121 122 if ! wait $wait_child; then 123 echo "Unable to confirm '$G_ORIG_CMD' success" \ 124 "within timeout period (${timeout}s)" 125 fi 126 127 if [ -n "$verbose_child" ]; then 128 kill $verbose_child 129 fi 130} 131 132function run_cmd() 133{ 134 local cmd="$*"; 135 136 if [ -n "$G_WAIT" ]; then 137 run_timeout "$G_WAIT" "$cmd" 138 else 139 $cmd 140 fi 141} 142 143function set_property() 144{ 145 run_cmd busctl set-property "$@" 146} 147 148function get_property() 149{ 150 G_WAIT="" 151 run_cmd busctl get-property "$@" 152} 153 154function state_query() 155{ 156 local state 157 state=$(get_property "$@" | cut -d '"' -f2) 158 printf "%-20s: %s\n" "$4" "$state" 159} 160 161function print_usage_err() 162{ 163 echo "ERROR: $1" >&2 164 echo "$USAGE" 165 exit 1 166} 167 168function mask_systemd_target() 169{ 170 target="$*" 171 systemctl mask "$target" 172} 173 174function unmask_systemd_target() 175{ 176 target="$*" 177 systemctl unmask "$target" 178} 179 180function disable_bmc_reboot() 181{ 182 dir="/run/systemd/system/" 183 file="reboot-guard.conf" 184 units=("reboot" "poweroff" "halt") 185 186 for unit in "${units[@]}"; do 187 mkdir -p "${dir}${unit}.target.d" 188 echo -e "[Unit]\nRefuseManualStart=yes" >> "${dir}${unit}.target.d/${file}" 189 done 190} 191 192function enable_bmc_reboot() 193{ 194 dir="/run/systemd/system/" 195 file="reboot-guard.conf" 196 units=("reboot" "poweroff" "halt") 197 198 for unit in "${units[@]}"; do 199 rm -rf "${dir}${unit}.target.d/${file}" 200 rm -rf "${dir}${unit}.target.d" 201 done 202} 203 204# will write blocking errors to stdout 205function check_boot_block_errors() 206{ 207 # array of boot block objects 208 blockArray=() 209 210 # Look for any objects under logging that implement the 211 # xyz.openbmc_project.Logging.ErrorBlocksTransition 212 subtree="$(busctl call xyz.openbmc_project.ObjectMapper \ 213 /xyz/openbmc_project/object_mapper \ 214 xyz.openbmc_project.ObjectMapper \ 215 GetSubTree sias "/xyz/openbmc_project/logging/" 0 1 \ 216 xyz.openbmc_project.Logging.ErrorBlocksTransition)" 217 218 # remove quotation marks 219 # shellcheck disable=SC2001 220 subtree="$(echo "$subtree" | sed 's/\"//g')" 221 222 for entry in $subtree; do 223 if [[ ${entry} =~ "xyz/openbmc_project/logging/block"* ]]; then 224 blockArray+=( "$entry" ) 225 fi 226 done 227 228 # now find associated error log for each boot block error 229 for berror in "${blockArray[@]}"; do 230 assocs="$(busctl call xyz.openbmc_project.Logging "$berror" \ 231 org.freedesktop.DBus.Properties Get \ 232 ss xyz.openbmc_project.Association.Definitions Associations)" 233 234 # remove quotation marks 235 # shellcheck disable=SC2001 236 assocs="$(echo "$assocs" | sed 's/\"//g')" 237 238 for entry in $assocs; do 239 if [[ ${entry} =~ "xyz/openbmc_project/logging/entry"* ]]; then 240 echo "Blocking Error: $entry" 241 fi 242 done 243 done 244} 245 246# helper function to check for boot block errors and notify user 247function check_and_warn_boot_block() 248{ 249 blockingErrors=$(check_boot_block_errors) 250 if [ -n "$blockingErrors" ]; then 251 echo !!!!!!!!!! 252 echo "WARNING! System has blocking errors that will prevent boot" 253 echo "$blockingErrors" 254 echo !!!!!!!!!! 255 fi 256} 257 258# list all phosphor-logging entries 259function list_logs() 260{ 261 # Look for any objects under logging that implement the 262 # xyz.openbmc_project.Logging.Entry 263 busctl -j call xyz.openbmc_project.ObjectMapper \ 264 /xyz/openbmc_project/object_mapper \ 265 xyz.openbmc_project.ObjectMapper \ 266 GetSubTreePaths sias "/xyz/openbmc_project/logging/" 0 1 \ 267 xyz.openbmc_project.Logging.Entry 268} 269 270# display input log details 271function show_log() 272{ 273 busctl -j call xyz.openbmc_project.Logging \ 274 "$1" \ 275 org.freedesktop.DBus.Properties \ 276 GetAll s xyz.openbmc_project.Logging.Entry 277} 278 279# delete all phosphor-logging entries 280function delete_logs() 281{ 282 busctl call xyz.openbmc_project.Logging \ 283 /xyz/openbmc_project/logging \ 284 xyz.openbmc_project.Collection.DeleteAll DeleteAll 285} 286 287# stop all targets associated with powering off a system 288function stop_off_targets() 289{ 290 systemctl stop \ 291 obmc-chassis-powered-off@0.target \ 292 obmc-host-stop-pre@0.target \ 293 obmc-host-stopped@0.target \ 294 obmc-host-stopping@0.target \ 295 obmc-power-off@0.target \ 296 obmc-power-stop-pre@0.target \ 297 obmc-power-stop@0.target 298} 299 300function handle_cmd() 301{ 302 case "$1" in 303 chassisoff) 304 OBJECT=$STATE_OBJECT/chassis$G_INSTANCE_ID 305 SERVICE=$(mapper get-service $OBJECT) 306 INTERFACE=$STATE_INTERFACE.Chassis 307 PROPERTY=RequestedPowerTransition 308 VALUE=$INTERFACE.Transition.Off 309 G_REQUESTED_STATE=$INTERFACE.PowerState.Off 310 G_QUERY="chassisstate" 311 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "s" $VALUE 312 ;; 313 chassison) 314 check_and_warn_boot_block 315 OBJECT=$STATE_OBJECT/chassis$G_INSTANCE_ID 316 SERVICE=$(mapper get-service $OBJECT) 317 INTERFACE=$STATE_INTERFACE.Chassis 318 PROPERTY=RequestedPowerTransition 319 VALUE=$INTERFACE.Transition.On 320 G_REQUESTED_STATE=$INTERFACE.PowerState.On 321 G_QUERY="chassisstate" 322 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "s" $VALUE 323 ;; 324 poweroff) 325 OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID 326 SERVICE=$(mapper get-service $OBJECT) 327 INTERFACE=$STATE_INTERFACE.Host 328 PROPERTY=RequestedHostTransition 329 VALUE=$INTERFACE.Transition.Off 330 G_REQUESTED_STATE=$INTERFACE.HostState.Off 331 G_QUERY="hoststate" 332 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "s" $VALUE 333 ;; 334 poweron) 335 check_and_warn_boot_block 336 OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID 337 SERVICE=$(mapper get-service $OBJECT) 338 INTERFACE=$STATE_INTERFACE.Host 339 PROPERTY=RequestedHostTransition 340 VALUE=$INTERFACE.Transition.On 341 G_REQUESTED_STATE=$INTERFACE.HostState.Running 342 G_QUERY="hoststate" 343 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "s" $VALUE 344 ;; 345 bmcstate) 346 OBJECT=$STATE_OBJECT/bmc0 347 SERVICE=$(mapper get-service $OBJECT) 348 INTERFACE=$STATE_INTERFACE.BMC 349 PROPERTY=CurrentBMCState 350 state_query "$SERVICE" $OBJECT $INTERFACE $PROPERTY 351 ;; 352 chassisstate) 353 OBJECT=$STATE_OBJECT/chassis$G_INSTANCE_ID 354 SERVICE=$(mapper get-service $OBJECT) 355 INTERFACE=$STATE_INTERFACE.Chassis 356 PROPERTY=CurrentPowerState 357 state_query "$SERVICE" $OBJECT $INTERFACE $PROPERTY 358 ;; 359 hoststate) 360 OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID 361 SERVICE=$(mapper get-service $OBJECT) 362 INTERFACE=$STATE_INTERFACE.Host 363 PROPERTY=CurrentHostState 364 state_query "$SERVICE" $OBJECT $INTERFACE $PROPERTY 365 ;; 366 osstate) 367 OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID 368 SERVICE=$(mapper get-service $OBJECT) 369 INTERFACE=$STATE_INTERFACE.OperatingSystem.Status 370 PROPERTY=OperatingSystemState 371 state_query "$SERVICE" $OBJECT $INTERFACE $PROPERTY 372 ;; 373 state|status) 374 for query in bmcstate chassisstate hoststate bootprogress osstate 375 do 376 handle_cmd $query 377 done 378 check_and_warn_boot_block 379 ;; 380 bootprogress) 381 OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID 382 SERVICE=$(mapper get-service $OBJECT) 383 INTERFACE=$STATE_INTERFACE.Boot.Progress 384 PROPERTY=BootProgress 385 state_query "$SERVICE" $OBJECT $INTERFACE $PROPERTY 386 ;; 387 power) 388 OBJECT=/org/openbmc/control/power0 389 SERVICE=$(mapper get-service $OBJECT) 390 INTERFACE=org.openbmc.control.Power 391 for property in pgood state pgood_timeout; do 392 # get_property can potentially return several 393 # different formats of values, so we do the parsing outside 394 # of get_property depending on the query. These queries 395 # return 'i VALUE' formatted strings. 396 STATE=$(get_property "$SERVICE" "$OBJECT" "$INTERFACE" "$property" | sed 's/i[ ^I]*//') 397 printf "%s = %s\n" $property "$STATE" 398 done 399 ;; 400 chassiskill) 401 /usr/libexec/chassiskill 402 ;; 403 hostrebootoff) 404 OBJECT=$CONTROL_OBJECT/host$G_INSTANCE_ID/auto_reboot 405 SERVICE=$(mapper get-service $OBJECT) 406 INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy 407 PROPERTY=AutoReboot 408 VALUE=false 409 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "b" $VALUE 410 ;; 411 hostrebootoffonetime) 412 OBJECT=$CONTROL_OBJECT/host$G_INSTANCE_ID/auto_reboot/one_time 413 SERVICE=$(mapper get-service $OBJECT) 414 INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy 415 PROPERTY=AutoReboot 416 VALUE=false 417 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "b" $VALUE 418 ;; 419 hostrebooton) 420 OBJECT=$CONTROL_OBJECT/host$G_INSTANCE_ID/auto_reboot 421 SERVICE=$(mapper get-service $OBJECT) 422 INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy 423 PROPERTY=AutoReboot 424 VALUE=true 425 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "b" $VALUE 426 ;; 427 bmcrebootoff) 428 disable_bmc_reboot 429 ;; 430 bmcrebooton) 431 enable_bmc_reboot 432 ;; 433 recoveryoff) 434 handle_cmd hostrebootoff 435 handle_cmd bmcrebootoff 436 mask_systemd_target $HOST_TIMEOUT_TARGET 437 mask_systemd_target $HOST_CRASH_TARGET 438 ;; 439 recoveryon) 440 handle_cmd hostrebooton 441 handle_cmd bmcrebooton 442 unmask_systemd_target $HOST_TIMEOUT_TARGET 443 unmask_systemd_target $HOST_CRASH_TARGET 444 ;; 445 listbootblock) 446 blockingErrors=$(check_boot_block_errors) 447 if [ -z "$blockingErrors" ]; then 448 echo "No blocking errors present" 449 else 450 echo "$blockingErrors" 451 fi 452 ;; 453 listlogs) 454 list_logs 455 ;; 456 showlog) 457 show_log "$2" 458 ;; 459 deletelogs) 460 delete_logs 461 ;; 462 stopofftargets) 463 stop_off_targets 464 ;; 465 *) 466 print_usage_err "Invalid command '$1'" 467 ;; 468 esac 469} 470 471shiftcnt=0 472for arg in "$@"; do 473 case $arg in 474 -w|--wait) 475 G_WAIT=30 476 shiftcnt=$((shiftcnt+1)) 477 continue 478 ;; 479 -h|--help) 480 print_help 481 ;; 482 -v|--verbose) 483 G_VERBOSE=y 484 shiftcnt=$((shiftcnt+1)) 485 ;; 486 -i=*|--id=*) 487 G_INSTANCE_ID="${arg#*=}" 488 shiftcnt=$((shiftcnt+1)) 489 ;; 490 -*) 491 print_usage_err "Unknown option: $arg" 492 ;; 493 *) 494 G_ORIG_CMD=$arg 495 # shift out the optional parameters 496 shift $shiftcnt 497 # pass all arguments to handle_cmd in case command takes additional 498 # parameters 499 handle_cmd "$@" 500 break 501 ;; 502 esac 503done 504