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