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