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