1#!/bin/sh -e
2
3set -euo pipefail
4
5OPTS="bmcstate,bootprogress,chassiskill,chassisoff,chassison,chassisstate,hoststate,\
6osstate,power,poweroff,poweron,state,status,rebootoff,rebooton,recoveryoff,recoveryon"
7
8USAGE="Usage: obmcutil [-h] [--wait] [--verbose]
9                {$OPTS}"
10
11INTERFACE_ROOT=xyz.openbmc_project
12STATE_INTERFACE=$INTERFACE_ROOT.State
13CONTROL_INTERFACE=$INTERFACE_ROOT.Control
14
15OBJECT_ROOT=/xyz/openbmc_project
16STATE_OBJECT=$OBJECT_ROOT/state
17CONTROL_OBJECT=$OBJECT_ROOT/control
18
19HOST_TIMEOUT_TARGET=obmc-host-timeout@0.target
20HOST_CRASH_TARGET=obmc-host-crash@0.target
21
22## NOTE: The following global variables are used only in the run_timeout cmd.
23## By declaring these globally instead of passing them through the
24## intermediary functions, which may not be "best practice", the readability
25## and cleanliness of the code should at least be increased.
26
27# The command passed in to be executed (e.g. poweron/off, status, etc.)
28# This will be be used in some instances of error reporting
29G_ORIG_CMD=
30# The state an interface should be in after executing the requested command.
31G_REQUESTED_STATE=
32# The query to run during a poweron/off or chassison/off to check that
33# the requested state (G_REQUESTED_STATE) of the interface has been reached.
34G_QUERY=
35# Wait the set period of time for state transitions to be successful before
36# continuing on with the program or reporting an error if timeout reached.
37G_WAIT=
38# Print the journal to the console
39G_VERBOSE=
40
41print_help ()
42{
43    echo "$USAGE"
44    echo ""
45    echo "positional arguments:"
46    echo "  {$OPTS}"
47    echo ""
48    echo "Examples:"
49    echo ""
50    echo "obmcutil rebootoff    Disable auto reboot from Quiesce state"
51    echo "obmcutil rebooton     Enable auto reboot from Quiesce state"
52    echo ""
53    echo "obmcutil recoveryoff  Disable handling boot watchdog timeout and host crash"
54    echo "                      Also, disable auto reboot from Quiesce state"
55    echo "obmcutil recoveryon   Enable handling boot watchdog timeout and host crash"
56    echo "                      Also, enable auto reboot from Quiesce state"
57    echo ""
58    echo "optional arguments:"
59    echo "  -h, --help          show this help message and exit"
60    echo "  -w, --wait          block until state transition succeeds or fails"
61    echo "  -v, --verbose       print the journal to stdout if --wait is supplied"
62    exit 0
63}
64
65run_timeout ()
66{
67    local timeout="$1"; shift
68    local cmd="$@"
69    local verbose_child=
70
71    if [ -n "$G_VERBOSE" ]; then
72        journalctl -f &
73        verbose_child=$!
74    fi
75
76    $cmd
77
78    # Run a background query for the transition to the expected state
79    # This will be killed if the transition doesn't succeed within
80    # a timeout period.
81    (
82        while ! grep -q $G_REQUESTED_STATE <<< $(handle_cmd $G_QUERY) ; do
83            sleep 1
84        done
85    ) &
86    wait_child=$!
87
88    # Could be bad if process is killed before 'timeout' occurs if
89    # transition doesn't succeed.
90    trap -- "" SIGTERM
91
92    # Workaround for lack of 'timeout' command.
93    (
94        sleep $timeout
95        kill $wait_child
96    ) > /dev/null 2>&1 &
97
98    if ! wait $wait_child; then
99        echo "Unable to confirm '$G_ORIG_CMD' success" \
100        "within timeout period (${timeout}s)"
101    fi
102
103    if [ -n $verbose_child ]; then
104        kill $verbose_child
105    fi
106}
107
108run_cmd ()
109{
110    local cmd="$@";
111
112    if [ -n "$G_WAIT" ]; then
113        run_timeout $G_WAIT "$cmd"
114    else
115        $cmd
116    fi
117}
118
119set_property ()
120{
121    run_cmd busctl set-property "$@"
122}
123
124get_property ()
125{
126    G_WAIT=""
127    run_cmd busctl get-property "$@"
128}
129
130state_query ()
131{
132    local state=$(get_property "$@" | cut -d '"' -f2)
133    printf "%-20s: %s\n" $4 $state
134}
135
136print_usage_err ()
137{
138    echo "ERROR: $1" >&2
139    echo "$USAGE"
140    exit 1
141}
142
143mask_systemd_target ()
144{
145    target="$@"
146    systemctl mask $target
147}
148
149unmask_systemd_target ()
150{
151    target="$@"
152    systemctl unmask $target
153}
154
155handle_cmd ()
156{
157    case "$1" in
158        chassisoff)
159            OBJECT=$STATE_OBJECT/chassis0
160            SERVICE=$(mapper get-service $OBJECT)
161            INTERFACE=$STATE_INTERFACE.Chassis
162            PROPERTY=RequestedPowerTransition
163            VALUE=$INTERFACE.Transition.Off
164            G_REQUESTED_STATE=$INTERFACE.PowerState.Off
165            G_QUERY="chassisstate"
166            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
167            ;;
168        chassison)
169            OBJECT=$STATE_OBJECT/chassis0
170            SERVICE=$(mapper get-service $OBJECT)
171            INTERFACE=$STATE_INTERFACE.Chassis
172            PROPERTY=RequestedPowerTransition
173            VALUE=$INTERFACE.Transition.On
174            G_REQUESTED_STATE=$INTERFACE.PowerState.On
175            G_QUERY="chassisstate"
176            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
177            ;;
178        poweroff)
179            OBJECT=$STATE_OBJECT/host0
180            SERVICE=$(mapper get-service $OBJECT)
181            INTERFACE=$STATE_INTERFACE.Host
182            PROPERTY=RequestedHostTransition
183            VALUE=$INTERFACE.Transition.Off
184            G_REQUESTED_STATE=$INTERFACE.HostState.Off
185            G_QUERY="hoststate"
186            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
187            ;;
188        poweron)
189            OBJECT=$STATE_OBJECT/host0
190            SERVICE=$(mapper get-service $OBJECT)
191            INTERFACE=$STATE_INTERFACE.Host
192            PROPERTY=RequestedHostTransition
193            VALUE=$INTERFACE.Transition.On
194            G_REQUESTED_STATE=$INTERFACE.HostState.Running
195            G_QUERY="hoststate"
196            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
197            ;;
198        bmcstate)
199            OBJECT=$STATE_OBJECT/bmc0
200            SERVICE=$(mapper get-service $OBJECT)
201            INTERFACE=$STATE_INTERFACE.BMC
202            PROPERTY=CurrentBMCState
203            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
204            ;;
205        chassisstate)
206            OBJECT=$STATE_OBJECT/chassis0
207            SERVICE=$(mapper get-service $OBJECT)
208            INTERFACE=$STATE_INTERFACE.Chassis
209            PROPERTY=CurrentPowerState
210            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
211            ;;
212        hoststate)
213            OBJECT=$STATE_OBJECT/host0
214            SERVICE=$(mapper get-service $OBJECT)
215            INTERFACE=$STATE_INTERFACE.Host
216            PROPERTY=CurrentHostState
217            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
218            ;;
219        osstate)
220            OBJECT=$STATE_OBJECT/host0
221            SERVICE=$(mapper get-service $OBJECT)
222            INTERFACE=$STATE_INTERFACE.OperatingSystem.Status
223            PROPERTY=OperatingSystemState
224            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
225            ;;
226        state|status)
227            for query in bmcstate chassisstate hoststate bootprogress osstate
228            do
229                handle_cmd $query
230            done
231            ;;
232        bootprogress)
233            OBJECT=$STATE_OBJECT/host0
234            SERVICE=$(mapper get-service $OBJECT)
235            INTERFACE=$STATE_INTERFACE.Boot.Progress
236            PROPERTY=BootProgress
237            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
238            ;;
239        power)
240            OBJECT=/org/openbmc/control/power0
241            SERVICE=$(mapper get-service $OBJECT)
242            INTERFACE=org.openbmc.control.Power
243            for property in pgood state pgood_timeout
244            do
245                # get_property can potentially return several
246                # different formats of values, so we do the parsing outside
247                # of get_property depending on the query. These queries
248                # return 'i VALUE' formatted strings.
249                STATE=$(get_property $SERVICE $OBJECT $INTERFACE $property \
250                    | sed 's/i[ ^I]*//')
251                printf "%s = %s\n" $property $STATE
252            done
253            ;;
254        chassiskill)
255            /usr/libexec/chassiskill
256            ;;
257        rebootoff)
258            OBJECT=$CONTROL_OBJECT/host0/auto_reboot
259            SERVICE=$(mapper get-service $OBJECT)
260            INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy
261            PROPERTY=AutoReboot
262            VALUE=false
263            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "b" $VALUE
264            ;;
265        rebooton)
266            OBJECT=$CONTROL_OBJECT/host0/auto_reboot
267            SERVICE=$(mapper get-service $OBJECT)
268            INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy
269            PROPERTY=AutoReboot
270            VALUE=true
271            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "b" $VALUE
272            ;;
273        recoveryoff)
274            handle_cmd rebootoff
275            mask_systemd_target $HOST_TIMEOUT_TARGET
276            mask_systemd_target $HOST_CRASH_TARGET
277            ;;
278        recoveryon)
279            handle_cmd rebooton
280            unmask_systemd_target $HOST_TIMEOUT_TARGET
281            unmask_systemd_target $HOST_CRASH_TARGET
282            ;;
283        *)
284            print_usage_err "Invalid command '$1'"
285            ;;
286    esac
287}
288
289for arg in "$@"; do
290    case $arg in
291        -w|--wait)
292            G_WAIT=30
293            continue
294            ;;
295        -h|--help)
296            print_help
297            ;;
298        -v|--verbose)
299            G_VERBOSE=y
300            ;;
301        -*)
302            print_usage_err "Unknown option: $arg"
303            ;;
304        *)
305            G_ORIG_CMD=$arg
306            handle_cmd $arg
307            break
308            ;;
309    esac
310done
311