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