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