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