Files
frameworks_base/services/java/com/android/server/NativeDaemonConnector.java
Kenny Root a34c9ca30e Add more error checking for ndc
In NativeDaemonConnector.doCommand() calls, there was inconsistent error
checking. This change adds error checking for every call and makes it so
that any call to .doCommand() that gets an error code won't cause the
code to hang forever.

Change-Id: If714282b6642f278fb8137f652af1a012670253b
2010-08-17 23:29:46 -07:00

291 lines
10 KiB
Java

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.net.LocalSocketAddress;
import android.net.LocalSocket;
import android.os.Environment;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Slog;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Generic connector class for interfacing with a native
* daemon which uses the libsysutils FrameworkListener
* protocol.
*/
final class NativeDaemonConnector implements Runnable {
private static final boolean LOCAL_LOGD = false;
private BlockingQueue<String> mResponseQueue;
private OutputStream mOutputStream;
private String TAG = "NativeDaemonConnector";
private String mSocket;
private INativeDaemonConnectorCallbacks mCallbacks;
private final int BUFFER_SIZE = 4096;
class ResponseCode {
public static final int ActionInitiated = 100;
public static final int CommandOkay = 200;
// The range of 400 -> 599 is reserved for cmd failures
public static final int OperationFailed = 400;
public static final int CommandSyntaxError = 500;
public static final int CommandParameterError = 501;
public static final int UnsolicitedInformational = 600;
//
public static final int FailedRangeStart = 400;
public static final int FailedRangeEnd = 599;
}
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
String socket, int responseQueueSize, String logTag) {
mCallbacks = callbacks;
if (logTag != null)
TAG = logTag;
mSocket = socket;
mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
}
public void run() {
while (true) {
try {
listenToSocket();
} catch (Exception e) {
Slog.e(TAG, "Error in NativeDaemonConnector", e);
SystemClock.sleep(5000);
}
}
}
private void listenToSocket() throws IOException {
LocalSocket socket = null;
try {
socket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress(mSocket,
LocalSocketAddress.Namespace.RESERVED);
socket.connect(address);
mCallbacks.onDaemonConnected();
InputStream inputStream = socket.getInputStream();
mOutputStream = socket.getOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int start = 0;
while (true) {
int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
if (count < 0) break;
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
String event = new String(buffer, start, i - start);
if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
String[] tokens = event.split(" ");
try {
int code = Integer.parseInt(tokens[0]);
if (code >= ResponseCode.UnsolicitedInformational) {
try {
if (!mCallbacks.onEvent(code, event, tokens)) {
Slog.w(TAG, String.format(
"Unhandled event (%s)", event));
}
} catch (Exception ex) {
Slog.e(TAG, String.format(
"Error handling '%s'", event), ex);
}
}
try {
mResponseQueue.put(event);
} catch (InterruptedException ex) {
Slog.e(TAG, "Failed to put response onto queue", ex);
}
} catch (NumberFormatException nfe) {
Slog.w(TAG, String.format("Bad msg (%s)", event));
}
start = i + 1;
}
}
if (start != count) {
final int remaining = BUFFER_SIZE - start;
System.arraycopy(buffer, start, buffer, 0, remaining);
start = remaining;
} else {
start = 0;
}
}
} catch (IOException ex) {
Slog.e(TAG, "Communications error", ex);
throw ex;
} finally {
synchronized (this) {
if (mOutputStream != null) {
try {
mOutputStream.close();
} catch (IOException e) {
Slog.w(TAG, "Failed closing output stream", e);
}
mOutputStream = null;
}
}
try {
if (socket != null) {
socket.close();
}
} catch (IOException ex) {
Slog.w(TAG, "Failed closing socket", ex);
}
}
}
private void sendCommand(String command) {
sendCommand(command, null);
}
/**
* Sends a command to the daemon with a single argument
*
* @param command The command to send to the daemon
* @param argument The argument to send with the command (or null)
*/
private void sendCommand(String command, String argument) {
synchronized (this) {
if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
if (mOutputStream == null) {
Slog.e(TAG, "No connection to daemon", new IllegalStateException());
} else {
StringBuilder builder = new StringBuilder(command);
if (argument != null) {
builder.append(argument);
}
builder.append('\0');
try {
mOutputStream.write(builder.toString().getBytes());
} catch (IOException ex) {
Slog.e(TAG, "IOException in sendCommand", ex);
}
}
}
}
/**
* Issue a command to the native daemon and return the responses
*/
public synchronized ArrayList<String> doCommand(String cmd)
throws NativeDaemonConnectorException {
sendCommand(cmd);
ArrayList<String> response = new ArrayList<String>();
boolean complete = false;
int code = -1;
while (!complete) {
try {
String line = mResponseQueue.take();
if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
String[] tokens = line.split(" ");
try {
code = Integer.parseInt(tokens[0]);
} catch (NumberFormatException nfe) {
throw new NativeDaemonConnectorException(
String.format("Invalid response from daemon (%s)", line));
}
if ((code >= 200) && (code < 600)) {
complete = true;
}
response.add(line);
} catch (InterruptedException ex) {
Slog.e(TAG, "Failed to process response", ex);
}
}
if (code >= ResponseCode.FailedRangeStart &&
code <= ResponseCode.FailedRangeEnd) {
/*
* Note: The format of the last response in this case is
* "NNN <errmsg>"
*/
throw new NativeDaemonConnectorException(
code, cmd, response.get(response.size()-1).substring(4));
}
return response;
}
/*
* Issues a list command and returns the cooked list
*/
public String[] doListCommand(String cmd, int expectedResponseCode)
throws NativeDaemonConnectorException {
ArrayList<String> rsp = doCommand(cmd);
String[] rdata = new String[rsp.size()-1];
int idx = 0;
for (int i = 0; i < rsp.size(); i++) {
String line = rsp.get(i);
try {
String[] tok = line.split(" ");
int code = Integer.parseInt(tok[0]);
if (code == expectedResponseCode) {
rdata[idx++] = line.substring(tok[0].length() + 1);
} else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
int last = rsp.size() -1;
if (i != last) {
Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
for (int j = i; j <= last ; j++) {
Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
}
}
return rdata;
} else {
throw new NativeDaemonConnectorException(
String.format("Expected list response %d, but got %d",
expectedResponseCode, code));
}
} catch (NumberFormatException nfe) {
throw new NativeDaemonConnectorException(
String.format("Error reading code '%s'", line));
}
}
throw new NativeDaemonConnectorException("Got an empty response");
}
}