• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server;
18 
19 import android.net.LocalSocketAddress;
20 import android.net.LocalSocket;
21 import android.os.Environment;
22 import android.os.SystemClock;
23 import android.os.SystemProperties;
24 import android.util.Slog;
25 
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.net.Socket;
30 
31 import java.util.List;
32 import java.util.ArrayList;
33 import java.util.ListIterator;
34 import java.util.concurrent.BlockingQueue;
35 import 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  */
42 final 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 
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag)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 
run()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 
listenToSocket()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                             } else {
136                                 try {
137                                     mResponseQueue.put(event);
138                                 } catch (InterruptedException ex) {
139                                     Slog.e(TAG, "Failed to put response onto queue", ex);
140                                 }
141                             }
142                         } catch (NumberFormatException nfe) {
143                             Slog.w(TAG, String.format("Bad msg (%s)", event));
144                         }
145                         start = i + 1;
146                     }
147                 }
148 
149                 // We should end at the amount we read. If not, compact then
150                 // buffer and read again.
151                 if (start != count) {
152                     final int remaining = BUFFER_SIZE - start;
153                     System.arraycopy(buffer, start, buffer, 0, remaining);
154                     start = remaining;
155                 } else {
156                     start = 0;
157                 }
158             }
159         } catch (IOException ex) {
160             Slog.e(TAG, "Communications error", ex);
161             throw ex;
162         } finally {
163             synchronized (this) {
164                 if (mOutputStream != null) {
165                     try {
166                         mOutputStream.close();
167                     } catch (IOException e) {
168                         Slog.w(TAG, "Failed closing output stream", e);
169                     }
170                     mOutputStream = null;
171                 }
172             }
173 
174             try {
175                 if (socket != null) {
176                     socket.close();
177                 }
178             } catch (IOException ex) {
179                 Slog.w(TAG, "Failed closing socket", ex);
180             }
181         }
182     }
183 
sendCommand(String command)184     private void sendCommand(String command)
185             throws NativeDaemonConnectorException  {
186         sendCommand(command, null);
187     }
188 
189     /**
190      * Sends a command to the daemon with a single argument
191      *
192      * @param command  The command to send to the daemon
193      * @param argument The argument to send with the command (or null)
194      */
sendCommand(String command, String argument)195     private void sendCommand(String command, String argument)
196             throws NativeDaemonConnectorException  {
197         synchronized (this) {
198             if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
199             if (mOutputStream == null) {
200                 Slog.e(TAG, "No connection to daemon", new IllegalStateException());
201                 throw new NativeDaemonConnectorException("No output stream!");
202             } else {
203                 StringBuilder builder = new StringBuilder(command);
204                 if (argument != null) {
205                     builder.append(argument);
206                 }
207                 builder.append('\0');
208 
209                 try {
210                     mOutputStream.write(builder.toString().getBytes());
211                 } catch (IOException ex) {
212                     Slog.e(TAG, "IOException in sendCommand", ex);
213                 }
214             }
215         }
216     }
217 
218     /**
219      * Issue a command to the native daemon and return the responses
220      */
doCommand(String cmd)221     public synchronized ArrayList<String> doCommand(String cmd)
222             throws NativeDaemonConnectorException  {
223         mResponseQueue.clear();
224         sendCommand(cmd);
225 
226         ArrayList<String> response = new ArrayList<String>();
227         boolean complete = false;
228         int code = -1;
229 
230         while (!complete) {
231             try {
232                 // TODO - this should not block forever
233                 String line = mResponseQueue.take();
234                 if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
235                 String[] tokens = line.split(" ");
236                 try {
237                     code = Integer.parseInt(tokens[0]);
238                 } catch (NumberFormatException nfe) {
239                     throw new NativeDaemonConnectorException(
240                             String.format("Invalid response from daemon (%s)", line));
241                 }
242 
243                 if ((code >= 200) && (code < 600)) {
244                     complete = true;
245                 }
246                 response.add(line);
247             } catch (InterruptedException ex) {
248                 Slog.e(TAG, "Failed to process response", ex);
249             }
250         }
251 
252         if (code >= ResponseCode.FailedRangeStart &&
253                 code <= ResponseCode.FailedRangeEnd) {
254             /*
255              * Note: The format of the last response in this case is
256              *        "NNN <errmsg>"
257              */
258             throw new NativeDaemonConnectorException(
259                     code, cmd, response.get(response.size()-1).substring(4));
260         }
261         return response;
262     }
263 
264     /*
265      * Issues a list command and returns the cooked list
266      */
doListCommand(String cmd, int expectedResponseCode)267     public String[] doListCommand(String cmd, int expectedResponseCode)
268             throws NativeDaemonConnectorException {
269 
270         ArrayList<String> rsp = doCommand(cmd);
271         String[] rdata = new String[rsp.size()-1];
272         int idx = 0;
273 
274         for (int i = 0; i < rsp.size(); i++) {
275             String line = rsp.get(i);
276             try {
277                 String[] tok = line.split(" ");
278                 int code = Integer.parseInt(tok[0]);
279                 if (code == expectedResponseCode) {
280                     rdata[idx++] = line.substring(tok[0].length() + 1);
281                 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
282                     if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
283                     int last = rsp.size() -1;
284                     if (i != last) {
285                         Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
286                         for (int j = i; j <= last ; j++) {
287                             Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
288                         }
289                     }
290                     return rdata;
291                 } else {
292                     throw new NativeDaemonConnectorException(
293                             String.format("Expected list response %d, but got %d",
294                                     expectedResponseCode, code));
295                 }
296             } catch (NumberFormatException nfe) {
297                 throw new NativeDaemonConnectorException(
298                         String.format("Error reading code '%s'", line));
299             }
300         }
301         throw new NativeDaemonConnectorException("Got an empty response");
302     }
303 }
304