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