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