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 44print_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 89run_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 132run_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 143set_property () 144{ 145 run_cmd busctl set-property "$@" 146} 147 148get_property () 149{ 150 G_WAIT="" 151 run_cmd busctl get-property "$@" 152} 153 154state_query () 155{ 156 local state 157 state=$(get_property "$@" | cut -d '"' -f2) 158 printf "%-20s: %s\n" "$4" "$state" 159} 160 161print_usage_err () 162{ 163 echo "ERROR: $1" >&2 164 echo "$USAGE" 165 exit 1 166} 167 168mask_systemd_target () 169{ 170 target="$*" 171 systemctl mask "$target" 172} 173 174unmask_systemd_target () 175{ 176 target="$*" 177 systemctl unmask "$target" 178} 179 180disable_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 192enable_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 205check_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 247check_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 259list_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 271show_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 280delete_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 288stop_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 300handle_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 392 do 393 # get_property can potentially return several 394 # different formats of values, so we do the parsing outside 395 # of get_property depending on the query. These queries 396 # return 'i VALUE' formatted strings. 397 STATE=$(get_property "$SERVICE" $OBJECT $INTERFACE $property \ 398 | sed 's/i[ ^I]*//') 399 printf "%s = %s\n" $property "$STATE" 400 done 401 ;; 402 chassiskill) 403 /usr/libexec/chassiskill 404 ;; 405 hostrebootoff) 406 OBJECT=$CONTROL_OBJECT/host$G_INSTANCE_ID/auto_reboot 407 SERVICE=$(mapper get-service $OBJECT) 408 INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy 409 PROPERTY=AutoReboot 410 VALUE=false 411 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "b" $VALUE 412 ;; 413 hostrebootoffonetime) 414 OBJECT=$CONTROL_OBJECT/host$G_INSTANCE_ID/auto_reboot/one_time 415 SERVICE=$(mapper get-service $OBJECT) 416 INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy 417 PROPERTY=AutoReboot 418 VALUE=false 419 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "b" $VALUE 420 ;; 421 hostrebooton) 422 OBJECT=$CONTROL_OBJECT/host$G_INSTANCE_ID/auto_reboot 423 SERVICE=$(mapper get-service $OBJECT) 424 INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy 425 PROPERTY=AutoReboot 426 VALUE=true 427 set_property "$SERVICE" $OBJECT $INTERFACE $PROPERTY "b" $VALUE 428 ;; 429 bmcrebootoff) 430 disable_bmc_reboot 431 ;; 432 bmcrebooton) 433 enable_bmc_reboot 434 ;; 435 recoveryoff) 436 handle_cmd hostrebootoff 437 handle_cmd bmcrebootoff 438 mask_systemd_target $HOST_TIMEOUT_TARGET 439 mask_systemd_target $HOST_CRASH_TARGET 440 ;; 441 recoveryon) 442 handle_cmd hostrebooton 443 handle_cmd bmcrebooton 444 unmask_systemd_target $HOST_TIMEOUT_TARGET 445 unmask_systemd_target $HOST_CRASH_TARGET 446 ;; 447 listbootblock) 448 blockingErrors=$(check_boot_block_errors) 449 if [ -z "$blockingErrors" ]; then 450 echo "No blocking errors present" 451 else 452 echo "$blockingErrors" 453 fi 454 ;; 455 listlogs) 456 list_logs 457 ;; 458 showlog) 459 show_log "$2" 460 ;; 461 deletelogs) 462 delete_logs 463 ;; 464 stopofftargets) 465 stop_off_targets 466 ;; 467 *) 468 print_usage_err "Invalid command '$1'" 469 ;; 470 esac 471} 472 473shiftcnt=0 474for arg in "$@"; do 475 case $arg in 476 -w|--wait) 477 G_WAIT=30 478 shiftcnt=$((shiftcnt+1)) 479 continue 480 ;; 481 -h|--help) 482 print_help 483 ;; 484 -v|--verbose) 485 G_VERBOSE=y 486 shiftcnt=$((shiftcnt+1)) 487 ;; 488 -i=*|--id=*) 489 G_INSTANCE_ID="${arg#*=}" 490 shiftcnt=$((shiftcnt+1)) 491 ;; 492 -*) 493 print_usage_err "Unknown option: $arg" 494 ;; 495 *) 496 G_ORIG_CMD=$arg 497 # shift out the optional parameters 498 shift $shiftcnt 499 # pass all arguments to handle_cmd in case command takes additional 500 # parameters 501 handle_cmd "$@" 502 break 503 ;; 504 esac 505done 506