xref: /openbmc/phosphor-state-manager/obmcutil (revision ff6af28d51cbc56f8e1e6e97b3d0d7c05dd23c96)
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