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