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