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