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]
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
34print_help ()
35{
36    echo "$USAGE"
37    echo ""
38    echo "positional arguments:"
39    echo "  {$OPTS}"
40    echo ""
41    echo "optional arguments:"
42    echo "  -h, --help          show this help message and exit"
43    echo "  -w, --wait          block until state transition succeeds or fails"
44    exit 0
45}
46
47run_timeout ()
48{
49    local timeout="$1"; shift
50    local cmd="$@"
51
52    $cmd
53
54    # Run a background query for the transition to the expected state
55    # This will be killed if the transition doesn't succeed within
56    # a timeout period.
57    (
58        while ! grep -q $G_REQUESTED_STATE <<< $(handle_cmd $G_QUERY) ; do
59            sleep 1
60        done
61    ) &
62    child=$!
63
64    # Could be bad if process is killed before 'timeout' occurs if
65    # transition doesn't succeed.
66    trap -- "" SIGTERM
67
68    # Workaround for lack of 'timeout' command.
69    (
70        sleep $timeout
71        kill $child
72    ) > /dev/null 2>&1 &
73
74    if ! wait $child; then
75        echo "Unable to confirm '$G_ORIG_CMD' success" \
76        "within timeout period (${timeout}s)"
77    fi
78}
79
80run_cmd ()
81{
82    local cmd="$@";
83
84    if [ -n "$G_WAIT" ]; then
85        run_timeout $G_WAIT "$cmd"
86    else
87        $cmd
88    fi
89}
90
91set_property ()
92{
93    run_cmd busctl set-property "$@"
94}
95
96get_property ()
97{
98    G_WAIT=""
99    run_cmd busctl get-property "$@"
100}
101
102state_query ()
103{
104    local state=$(get_property "$@" | cut -d '"' -f2)
105    printf "%-20s: %s\n" $4 $state
106}
107
108print_usage_err ()
109{
110    echo "ERROR: $1" >&2
111    echo "$USAGE"
112    exit 1
113}
114
115handle_cmd ()
116{
117    case "$1" in
118        chassisoff)
119            OBJECT=$STATE_OBJECT/chassis0
120            SERVICE=$(mapper get-service $OBJECT)
121            INTERFACE=$STATE_INTERFACE.Chassis
122            PROPERTY=RequestedPowerTransition
123            VALUE=$INTERFACE.Transition.Off
124            G_REQUESTED_STATE=$INTERFACE.PowerState.Off
125            G_QUERY="chassisstate"
126            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
127            ;;
128        chassison)
129            OBJECT=$STATE_OBJECT/chassis0
130            SERVICE=$(mapper get-service $OBJECT)
131            INTERFACE=$STATE_INTERFACE.Chassis
132            PROPERTY=RequestedPowerTransition
133            VALUE=$INTERFACE.Transition.On
134            G_REQUESTED_STATE=$INTERFACE.PowerState.On
135            G_QUERY="chassisstate"
136            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
137            ;;
138        poweroff)
139            OBJECT=$STATE_OBJECT/host0
140            SERVICE=$(mapper get-service $OBJECT)
141            INTERFACE=$STATE_INTERFACE.Host
142            PROPERTY=RequestedHostTransition
143            VALUE=$INTERFACE.Transition.Off
144            G_REQUESTED_STATE=$INTERFACE.HostState.Off
145            G_QUERY="hoststate"
146            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
147            ;;
148        poweron)
149            OBJECT=$STATE_OBJECT/host0
150            SERVICE=$(mapper get-service $OBJECT)
151            INTERFACE=$STATE_INTERFACE.Host
152            PROPERTY=RequestedHostTransition
153            VALUE=$INTERFACE.Transition.On
154            G_REQUESTED_STATE=$INTERFACE.HostState.Running
155            G_QUERY="hoststate"
156            set_property $SERVICE $OBJECT $INTERFACE $PROPERTY "s" $VALUE
157            ;;
158        bmcstate)
159            OBJECT=$STATE_OBJECT/bmc0
160            SERVICE=$(mapper get-service $OBJECT)
161            INTERFACE=$STATE_INTERFACE.BMC
162            PROPERTY=CurrentBMCState
163            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
164            ;;
165        chassisstate)
166            OBJECT=$STATE_OBJECT/chassis0
167            SERVICE=$(mapper get-service $OBJECT)
168            INTERFACE=$STATE_INTERFACE.Chassis
169            PROPERTY=CurrentPowerState
170            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
171            ;;
172        hoststate)
173            OBJECT=$STATE_OBJECT/host0
174            SERVICE=$(mapper get-service $OBJECT)
175            INTERFACE=$STATE_INTERFACE.Host
176            PROPERTY=CurrentHostState
177            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
178            ;;
179        osstate)
180            OBJECT=$STATE_OBJECT/host0
181            SERVICE=$(mapper get-service $OBJECT)
182            INTERFACE=$STATE_INTERFACE.OperatingSystem.Status
183            PROPERTY=OperatingSystemState
184            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
185            ;;
186        state|status)
187            for query in bmcstate chassisstate hoststate bootprogress osstate
188            do
189                handle_cmd $query
190            done
191            ;;
192        bootprogress)
193            OBJECT=$STATE_OBJECT/host0
194            SERVICE=$(mapper get-service $OBJECT)
195            INTERFACE=$STATE_INTERFACE.Boot.Progress
196            PROPERTY=BootProgress
197            state_query $SERVICE $OBJECT $INTERFACE $PROPERTY
198            ;;
199        power)
200            OBJECT=/org/openbmc/control/power0
201            SERVICE=$(mapper get-service $OBJECT)
202            INTERFACE=org.openbmc.control.Power
203            for property in pgood state pgood_timeout
204            do
205                # get_property can potentially return several
206                # different formats of values, so we do the parsing outside
207                # of get_property depending on the query. These queries
208                # return 'i VALUE' formatted strings.
209                STATE=$(get_property $SERVICE $OBJECT $INTERFACE $property \
210                    | sed 's/i[ ^I]*//')
211                printf "%s = %s\n" $property $STATE
212            done
213            ;;
214        chassiskill)
215            /usr/libexec/chassiskill
216            ;;
217        *)
218            print_usage_err "Invalid command '$1'"
219            ;;
220    esac
221}
222
223for arg in "$@"; do
224    case $arg in
225        -w|--wait)
226            G_WAIT=30
227            continue
228            ;;
229        -h|--help)
230            print_help
231            ;;
232        -*)
233            print_usage_err "Unknown option: $arg"
234            ;;
235        *)
236            G_ORIG_CMD=$arg
237            handle_cmd $arg
238            break
239            ;;
240    esac
241done
242