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