NativeDaemonConnector.java revision e5c3afb29241fd3faae309f973645d7f6a7ed111
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.net.LocalSocketAddress;
20import android.net.LocalSocket;
21import android.os.Environment;
22import android.os.SystemClock;
23import android.os.SystemProperties;
24import android.util.Slog;
25
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.net.Socket;
30
31import java.util.List;
32import java.util.ArrayList;
33import java.util.ListIterator;
34import java.util.concurrent.BlockingQueue;
35import java.util.concurrent.LinkedBlockingQueue;
36
37/**
38 * Generic connector class for interfacing with a native
39 * daemon which uses the libsysutils FrameworkListener
40 * protocol.
41 */
42final class NativeDaemonConnector implements Runnable {
43    private static final boolean LOCAL_LOGD = false;
44
45    private BlockingQueue<String> mResponseQueue;
46    private OutputStream          mOutputStream;
47    private String                TAG = "NativeDaemonConnector";
48    private String                mSocket;
49    private INativeDaemonConnectorCallbacks mCallbacks;
50
51    private final int BUFFER_SIZE = 4096;
52
53    class ResponseCode {
54        public static final int ActionInitiated                = 100;
55
56        public static final int CommandOkay                    = 200;
57
58        // The range of 400 -> 599 is reserved for cmd failures
59        public static final int OperationFailed                = 400;
60        public static final int CommandSyntaxError             = 500;
61        public static final int CommandParameterError          = 501;
62
63        public static final int UnsolicitedInformational       = 600;
64
65        //
66        public static final int FailedRangeStart               = 400;
67        public static final int FailedRangeEnd                 = 599;
68    }
69
70    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
71                          String socket, int responseQueueSize, String logTag) {
72        mCallbacks = callbacks;
73        if (logTag != null)
74            TAG = logTag;
75        mSocket = socket;
76        mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
77    }
78
79    public void run() {
80
81        while (true) {
82            try {
83                listenToSocket();
84            } catch (Exception e) {
85                Slog.e(TAG, "Error in NativeDaemonConnector", e);
86                SystemClock.sleep(5000);
87            }
88        }
89    }
90
91    private void listenToSocket() throws IOException {
92        LocalSocket socket = null;
93
94        try {
95            socket = new LocalSocket();
96            LocalSocketAddress address = new LocalSocketAddress(mSocket,
97                    LocalSocketAddress.Namespace.RESERVED);
98
99            socket.connect(address);
100            mCallbacks.onDaemonConnected();
101
102            InputStream inputStream = socket.getInputStream();
103            mOutputStream = socket.getOutputStream();
104
105            byte[] buffer = new byte[BUFFER_SIZE];
106            int start = 0;
107
108            while (true) {
109                int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
110                if (count < 0) break;
111
112                // Add our starting point to the count and reset the start.
113                count += start;
114                start = 0;
115
116                for (int i = 0; i < count; i++) {
117                    if (buffer[i] == 0) {
118                        String event = new String(buffer, start, i - start);
119                        if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
120
121                        String[] tokens = event.split(" ");
122                        try {
123                            int code = Integer.parseInt(tokens[0]);
124
125                            if (code >= ResponseCode.UnsolicitedInformational) {
126                                try {
127                                    if (!mCallbacks.onEvent(code, event, tokens)) {
128                                        Slog.w(TAG, String.format(
129                                                "Unhandled event (%s)", event));
130                                    }
131                                } catch (Exception ex) {
132                                    Slog.e(TAG, String.format(
133                                            "Error handling '%s'", event), ex);
134                                }
135                            }
136                            try {
137                                mResponseQueue.put(event);
138                            } catch (InterruptedException ex) {
139                                Slog.e(TAG, "Failed to put response onto queue", ex);
140                            }
141                        } catch (NumberFormatException nfe) {
142                            Slog.w(TAG, String.format("Bad msg (%s)", event));
143                        }
144                        start = i + 1;
145                    }
146                }
147
148                // We should end at the amount we read. If not, compact then
149                // buffer and read again.
150                if (start != count) {
151                    final int remaining = BUFFER_SIZE - start;
152                    System.arraycopy(buffer, start, buffer, 0, remaining);
153                    start = remaining;
154                } else {
155                    start = 0;
156                }
157            }
158        } catch (IOException ex) {
159            Slog.e(TAG, "Communications error", ex);
160            throw ex;
161        } finally {
162            synchronized (this) {
163                if (mOutputStream != null) {
164                    try {
165                        mOutputStream.close();
166                    } catch (IOException e) {
167                        Slog.w(TAG, "Failed closing output stream", e);
168                    }
169                    mOutputStream = null;
170                }
171            }
172
173            try {
174                if (socket != null) {
175                    socket.close();
176                }
177            } catch (IOException ex) {
178                Slog.w(TAG, "Failed closing socket", ex);
179            }
180        }
181    }
182
183    private void sendCommand(String command)
184            throws NativeDaemonConnectorException  {
185        sendCommand(command, null);
186    }
187
188    /**
189     * Sends a command to the daemon with a single argument
190     *
191     * @param command  The command to send to the daemon
192     * @param argument The argument to send with the command (or null)
193     */
194    private void sendCommand(String command, String argument)
195            throws NativeDaemonConnectorException  {
196        synchronized (this) {
197            if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
198            if (mOutputStream == null) {
199                Slog.e(TAG, "No connection to daemon", new IllegalStateException());
200                throw new NativeDaemonConnectorException("No output stream!");
201            } else {
202                StringBuilder builder = new StringBuilder(command);
203                if (argument != null) {
204                    builder.append(argument);
205                }
206                builder.append('\0');
207
208                try {
209                    mOutputStream.write(builder.toString().getBytes());
210                } catch (IOException ex) {
211                    Slog.e(TAG, "IOException in sendCommand", ex);
212                }
213            }
214        }
215    }
216
217    /**
218     * Issue a command to the native daemon and return the responses
219     */
220    public synchronized ArrayList<String> doCommand(String cmd)
221            throws NativeDaemonConnectorException  {
222        sendCommand(cmd);
223
224        ArrayList<String> response = new ArrayList<String>();
225        boolean complete = false;
226        int code = -1;
227
228        while (!complete) {
229            try {
230                // TODO - this should not block forever
231                String line = mResponseQueue.take();
232                if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
233                String[] tokens = line.split(" ");
234                try {
235                    code = Integer.parseInt(tokens[0]);
236                } catch (NumberFormatException nfe) {
237                    throw new NativeDaemonConnectorException(
238                            String.format("Invalid response from daemon (%s)", line));
239                }
240
241                if ((code >= 200) && (code < 600)) {
242                    complete = true;
243                }
244                response.add(line);
245            } catch (InterruptedException ex) {
246                Slog.e(TAG, "Failed to process response", ex);
247            }
248        }
249
250        if (code >= ResponseCode.FailedRangeStart &&
251                code <= ResponseCode.FailedRangeEnd) {
252            /*
253             * Note: The format of the last response in this case is
254             *        "NNN <errmsg>"
255             */
256            throw new NativeDaemonConnectorException(
257                    code, cmd, response.get(response.size()-1).substring(4));
258        }
259        return response;
260    }
261
262    /*
263     * Issues a list command and returns the cooked list
264     */
265    public String[] doListCommand(String cmd, int expectedResponseCode)
266            throws NativeDaemonConnectorException {
267
268        ArrayList<String> rsp = doCommand(cmd);
269        String[] rdata = new String[rsp.size()-1];
270        int idx = 0;
271
272        for (int i = 0; i < rsp.size(); i++) {
273            String line = rsp.get(i);
274            try {
275                String[] tok = line.split(" ");
276                int code = Integer.parseInt(tok[0]);
277                if (code == expectedResponseCode) {
278                    rdata[idx++] = line.substring(tok[0].length() + 1);
279                } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
280                    if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
281                    int last = rsp.size() -1;
282                    if (i != last) {
283                        Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
284                        for (int j = i; j <= last ; j++) {
285                            Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
286                        }
287                    }
288                    return rdata;
289                } else {
290                    throw new NativeDaemonConnectorException(
291                            String.format("Expected list response %d, but got %d",
292                                    expectedResponseCode, code));
293                }
294            } catch (NumberFormatException nfe) {
295                throw new NativeDaemonConnectorException(
296                        String.format("Error reading code '%s'", line));
297            }
298        }
299        throw new NativeDaemonConnectorException("Got an empty response");
300    }
301}
302