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"
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 "optional arguments:"
64    echo "  -h, --help          show this help message and exit"
65    echo "  -w, --wait          block until state transition succeeds or fails"
66    echo "  -v, --verbose       print the journal to stdout if --wait is supplied"
67    exit 0
68}
69
70run_timeout ()
71{
72    local timeout="$1"; shift
73    local cmd="$@"
74    local verbose_child=
75
76    if [ -n "$G_VERBOSE" ]; then
77        journalctl -f &
78        verbose_child=$!
79    fi
80
81    $cmd
82
83    # Run a background query for the transition to the expected state
84    # This will be killed if the transition doesn't succeed within
85    # a timeout period.
86    (
87        while ! grep -q $G_REQUESTED_STATE <<< $(handle_cmd $G_QUERY) ; do
88            sleep 1
89        done
90    ) &
91    wait_child=$!
92
93    # Could be bad if process is killed before 'timeout' occurs if
94    # transition doesn't succeed.
95    trap -- "" SIGTERM
96
97    # Workaround for lack of 'timeout' command.
98    (
99        sleep $timeout
100        kill $wait_child
101    ) > /dev/null 2>&1 &
102
103    if ! wait $wait_child; then
104        echo "Unable to confirm '$G_ORIG_CMD' success" \
105        "within timeout period (${timeout}s)"
106    fi
107
108    if [ -n "$verbose_child" ]; then
109        kill $verbose_child
110    fi
111}
112
113run_cmd ()
114{
115    local cmd="$@";
116
117    if [ -n "$G_WAIT" ]; then
118        run_timeout $G_WAIT "$cmd"
119    else
120        $cmd
121    fi
122}
123
124set_property ()
125{
126    run_cmd busctl set-property "$@"
127}
128
129get_property ()
130{
131    G_WAIT=""
132    run_cmd busctl get-property "$@"
133}
134
135state_query ()
136{
137    local state=$(get_property "$@" | cut -d '"' -f2)
138    printf "%-20s: %s\n" $4 $state
139}
140
141print_usage_err ()
142{
143    echo "ERROR: $1" >&2
144    echo "$USAGE"
145    exit 1
146}
147
148mask_systemd_target ()
149{
150    target="$@"
151    systemctl mask $target
152}
153
154unmask_systemd_target ()
155{
156    target="$@"
157    systemctl unmask $target
158}
159
160disable_bmc_reboot ()
161{
162    dir="/run/systemd/system/"
163    file="reboot-guard.conf"
164    units=("reboot" "poweroff" "halt")
165
166    for unit in "${units[@]}"; do
167        mkdir -p ${dir}${unit}.target.d
168        echo -e "[Unit]\nRefuseManualStart=yes" >> ${dir}${unit}.target.d/${file}
169    done
170}
171
172enable_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        rm -rf ${dir}${unit}.target.d/${file}
180        rm -rf ${dir}${unit}.target.d
181    done
182}
183
184handle_cmd ()
185{
186    case "$1" in
187        chassisoff)
188            OBJECT=$STATE_OBJECT/chassis0
189            SERVICE=$(mapper get-service $OBJECT)
190            INTERFACE=$STATE_INTERFACE.Chassis
191            PROPERTY=RequestedPowerTransition
192            VALUE=$INTERFACE.Transition.Off
193            G_REQUESTED_STATE=$INTERFACE.PowerState.Off
194            G_QUERY="chassisstate"
195            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
196            ;;
197        chassison)
198            OBJECT=$STATE_OBJECT/chassis0
199            SERVICE=$(mapper get-service $OBJECT)
200            INTERFACE=$STATE_INTERFACE.Chassis
201            PROPERTY=RequestedPowerTransition
202            VALUE=$INTERFACE.Transition.On
203            G_REQUESTED_STATE=$INTERFACE.PowerState.On
204            G_QUERY="chassisstate"
205            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
206            ;;
207        poweroff)
208            OBJECT=$STATE_OBJECT/host0
209            SERVICE=$(mapper get-service $OBJECT)
210            INTERFACE=$STATE_INTERFACE.Host
211            PROPERTY=RequestedHostTransition
212            VALUE=$INTERFACE.Transition.Off
213            G_REQUESTED_STATE=$INTERFACE.HostState.Off
214            G_QUERY="hoststate"
215            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
216            ;;
217        poweron)
218            OBJECT=$STATE_OBJECT/host0
219            SERVICE=$(mapper get-service $OBJECT)
220            INTERFACE=$STATE_INTERFACE.Host
221            PROPERTY=RequestedHostTransition
222            VALUE=$INTERFACE.Transition.On
223            G_REQUESTED_STATE=$INTERFACE.HostState.Running
224            G_QUERY="hoststate"
225            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
226            ;;
227        bmcstate)
228            OBJECT=$STATE_OBJECT/bmc0
229            SERVICE=$(mapper get-service $OBJECT)
230            INTERFACE=$STATE_INTERFACE.BMC
231            PROPERTY=CurrentBMCState
232            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
233            ;;
234        chassisstate)
235            OBJECT=$STATE_OBJECT/chassis0
236            SERVICE=$(mapper get-service $OBJECT)
237            INTERFACE=$STATE_INTERFACE.Chassis
238            PROPERTY=CurrentPowerState
239            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
240            ;;
241        hoststate)
242            OBJECT=$STATE_OBJECT/host0
243            SERVICE=$(mapper get-service $OBJECT)
244            INTERFACE=$STATE_INTERFACE.Host
245            PROPERTY=CurrentHostState
246            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
247            ;;
248        osstate)
249            OBJECT=$STATE_OBJECT/host0
250            SERVICE=$(mapper get-service $OBJECT)
251            INTERFACE=$STATE_INTERFACE.OperatingSystem.Status
252            PROPERTY=OperatingSystemState
253            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
254            ;;
255        state|status)
256            for query in bmcstate chassisstate hoststate bootprogress osstate
257            do
258                handle_cmd $query
259            done
260            ;;
261        bootprogress)
262            OBJECT=$STATE_OBJECT/host0
263            SERVICE=$(mapper get-service $OBJECT)
264            INTERFACE=$STATE_INTERFACE.Boot.Progress
265            PROPERTY=BootProgress
266            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
267            ;;
268        power)
269            OBJECT=/org/openbmc/control/power0
270            SERVICE=$(mapper get-service $OBJECT)
271            INTERFACE=org.openbmc.control.Power
272            for property in pgood state pgood_timeout
273            do
274                # get_property can potentially return several
275                # different formats of values, so we do the parsing outside
276                # of get_property depending on the query. These queries
277                # return 'i VALUE' formatted strings.
278                STATE=$(get_property $SERVICE $OBJECT $INTERFACE $property \
279                    | sed 's/i[ ^I]*//')
280                printf "%s = %s\n" $property $STATE
281            done
282            ;;
283        chassiskill)
284            /usr/libexec/chassiskill
285            ;;
286        hostrebootoff)
287            OBJECT=$CONTROL_OBJECT/host0/auto_reboot
288            SERVICE=$(mapper get-service $OBJECT)
289            INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy
290            PROPERTY=AutoReboot
291            VALUE=false
292            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "b" $VALUE
293            ;;
294        hostrebooton)
295            OBJECT=$CONTROL_OBJECT/host0/auto_reboot
296            SERVICE=$(mapper get-service $OBJECT)
297            INTERFACE=$CONTROL_INTERFACE.Boot.RebootPolicy
298            PROPERTY=AutoReboot
299            VALUE=true
300            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "b" $VALUE
301            ;;
302        bmcrebootoff)
303            disable_bmc_reboot
304            ;;
305        bmcrebooton)
306            enable_bmc_reboot
307            ;;
308        recoveryoff)
309            handle_cmd hostrebootoff
310            handle_cmd bmcrebootoff
311            mask_systemd_target $HOST_TIMEOUT_TARGET
312            mask_systemd_target $HOST_CRASH_TARGET
313            ;;
314        recoveryon)
315            handle_cmd hostrebooton
316            handle_cmd bmcrebooton
317            unmask_systemd_target $HOST_TIMEOUT_TARGET
318            unmask_systemd_target $HOST_CRASH_TARGET
319            ;;
320        *)
321            print_usage_err "Invalid command '$1'"
322            ;;
323    esac
324}
325
326for arg in "$@"; do
327    case $arg in
328        -w|--wait)
329            G_WAIT=30
330            continue
331            ;;
332        -h|--help)
333            print_help
334            ;;
335        -v|--verbose)
336            G_VERBOSE=y
337            ;;
338        -*)
339            print_usage_err "Unknown option: $arg"
340            ;;
341        *)
342            G_ORIG_CMD=$arg
343            handle_cmd $arg
344            break
345            ;;
346    esac
347done
348