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 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 } 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 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 */ 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 */ 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 */ 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