Home | History | Annotate | Download | only in server
      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.LocalSocket;
     20 import android.net.LocalSocketAddress;
     21 import android.os.Handler;
     22 import android.os.HandlerThread;
     23 import android.os.Message;
     24 import android.os.SystemClock;
     25 import android.util.Slog;
     26 
     27 import java.io.IOException;
     28 import java.io.InputStream;
     29 import java.io.OutputStream;
     30 import java.util.ArrayList;
     31 import java.util.concurrent.BlockingQueue;
     32 import java.util.concurrent.LinkedBlockingQueue;
     33 
     34 /**
     35  * Generic connector class for interfacing with a native
     36  * daemon which uses the libsysutils FrameworkListener
     37  * protocol.
     38  */
     39 final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
     40     private static final boolean LOCAL_LOGD = false;
     41 
     42     private BlockingQueue<String> mResponseQueue;
     43     private OutputStream          mOutputStream;
     44     private String                TAG = "NativeDaemonConnector";
     45     private String                mSocket;
     46     private INativeDaemonConnectorCallbacks mCallbacks;
     47     private Handler               mCallbackHandler;
     48 
     49     /** Lock held whenever communicating with native daemon. */
     50     private Object mDaemonLock = new Object();
     51 
     52     private final int BUFFER_SIZE = 4096;
     53 
     54     class ResponseCode {
     55         public static final int ActionInitiated                = 100;
     56 
     57         public static final int CommandOkay                    = 200;
     58 
     59         // The range of 400 -> 599 is reserved for cmd failures
     60         public static final int OperationFailed                = 400;
     61         public static final int CommandSyntaxError             = 500;
     62         public static final int CommandParameterError          = 501;
     63 
     64         public static final int UnsolicitedInformational       = 600;
     65 
     66         //
     67         public static final int FailedRangeStart               = 400;
     68         public static final int FailedRangeEnd                 = 599;
     69     }
     70 
     71     NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
     72                           String socket, int responseQueueSize, String logTag) {
     73         mCallbacks = callbacks;
     74         if (logTag != null)
     75             TAG = logTag;
     76         mSocket = socket;
     77         mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
     78     }
     79 
     80     @Override
     81     public void run() {
     82         HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
     83         thread.start();
     84         mCallbackHandler = new Handler(thread.getLooper(), this);
     85 
     86         while (true) {
     87             try {
     88                 listenToSocket();
     89             } catch (Exception e) {
     90                 Slog.e(TAG, "Error in NativeDaemonConnector", e);
     91                 SystemClock.sleep(5000);
     92             }
     93         }
     94     }
     95 
     96     @Override
     97     public boolean handleMessage(Message msg) {
     98         String event = (String) msg.obj;
     99         try {
    100             if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
    101                 Slog.w(TAG, String.format(
    102                         "Unhandled event '%s'", event));
    103             }
    104         } catch (Exception e) {
    105             Slog.e(TAG, String.format(
    106                     "Error handling '%s'", event), e);
    107         }
    108         return true;
    109     }
    110 
    111     private void listenToSocket() throws IOException {
    112         LocalSocket socket = null;
    113 
    114         try {
    115             socket = new LocalSocket();
    116             LocalSocketAddress address = new LocalSocketAddress(mSocket,
    117                     LocalSocketAddress.Namespace.RESERVED);
    118 
    119             socket.connect(address);
    120 
    121             InputStream inputStream = socket.getInputStream();
    122             mOutputStream = socket.getOutputStream();
    123 
    124             mCallbacks.onDaemonConnected();
    125 
    126             byte[] buffer = new byte[BUFFER_SIZE];
    127             int start = 0;
    128 
    129             while (true) {
    130                 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
    131                 if (count < 0) break;
    132 
    133                 // Add our starting point to the count and reset the start.
    134                 count += start;
    135                 start = 0;
    136 
    137                 for (int i = 0; i < count; i++) {
    138                     if (buffer[i] == 0) {
    139                         String event = new String(buffer, start, i - start);
    140                         if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
    141 
    142                         String[] tokens = event.split(" ", 2);
    143                         try {
    144                             int code = Integer.parseInt(tokens[0]);
    145 
    146                             if (code >= ResponseCode.UnsolicitedInformational) {
    147                                 mCallbackHandler.sendMessage(
    148                                         mCallbackHandler.obtainMessage(code, event));
    149                             } else {
    150                                 try {
    151                                     mResponseQueue.put(event);
    152                                 } catch (InterruptedException ex) {
    153                                     Slog.e(TAG, "Failed to put response onto queue", ex);
    154                                 }
    155                             }
    156                         } catch (NumberFormatException nfe) {
    157                             Slog.w(TAG, String.format("Bad msg (%s)", event));
    158                         }
    159                         start = i + 1;
    160                     }
    161                 }
    162 
    163                 // We should end at the amount we read. If not, compact then
    164                 // buffer and read again.
    165                 if (start != count) {
    166                     final int remaining = BUFFER_SIZE - start;
    167                     System.arraycopy(buffer, start, buffer, 0, remaining);
    168                     start = remaining;
    169                 } else {
    170                     start = 0;
    171                 }
    172             }
    173         } catch (IOException ex) {
    174             Slog.e(TAG, "Communications error", ex);
    175             throw ex;
    176         } finally {
    177             synchronized (mDaemonLock) {
    178                 if (mOutputStream != null) {
    179                     try {
    180                         mOutputStream.close();
    181                     } catch (IOException e) {
    182                         Slog.w(TAG, "Failed closing output stream", e);
    183                     }
    184                     mOutputStream = null;
    185                 }
    186             }
    187 
    188             try {
    189                 if (socket != null) {
    190                     socket.close();
    191                 }
    192             } catch (IOException ex) {
    193                 Slog.w(TAG, "Failed closing socket", ex);
    194             }
    195         }
    196     }
    197 
    198     private void sendCommandLocked(String command) throws NativeDaemonConnectorException {
    199         sendCommandLocked(command, null);
    200     }
    201 
    202     /**
    203      * Sends a command to the daemon with a single argument
    204      *
    205      * @param command  The command to send to the daemon
    206      * @param argument The argument to send with the command (or null)
    207      */
    208     private void sendCommandLocked(String command, String argument)
    209             throws NativeDaemonConnectorException {
    210         if (command != null && command.indexOf('\0') >= 0) {
    211             throw new IllegalArgumentException("unexpected command: " + command);
    212         }
    213         if (argument != null && argument.indexOf('\0') >= 0) {
    214             throw new IllegalArgumentException("unexpected argument: " + argument);
    215         }
    216 
    217         if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
    218         if (mOutputStream == null) {
    219             Slog.e(TAG, "No connection to daemon", new IllegalStateException());
    220             throw new NativeDaemonConnectorException("No output stream!");
    221         } else {
    222             StringBuilder builder = new StringBuilder(command);
    223             if (argument != null) {
    224                 builder.append(argument);
    225             }
    226             builder.append('\0');
    227 
    228             try {
    229                 mOutputStream.write(builder.toString().getBytes());
    230             } catch (IOException ex) {
    231                 Slog.e(TAG, "IOException in sendCommand", ex);
    232             }
    233         }
    234     }
    235 
    236     /**
    237      * Issue a command to the native daemon and return the responses
    238      */
    239     public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
    240         synchronized (mDaemonLock) {
    241             return doCommandLocked(cmd);
    242         }
    243     }
    244 
    245     private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException {
    246         mResponseQueue.clear();
    247         sendCommandLocked(cmd);
    248 
    249         ArrayList<String> response = new ArrayList<String>();
    250         boolean complete = false;
    251         int code = -1;
    252 
    253         while (!complete) {
    254             try {
    255                 // TODO - this should not block forever
    256                 String line = mResponseQueue.take();
    257                 if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
    258                 String[] tokens = line.split(" ");
    259                 try {
    260                     code = Integer.parseInt(tokens[0]);
    261                 } catch (NumberFormatException nfe) {
    262                     throw new NativeDaemonConnectorException(
    263                             String.format("Invalid response from daemon (%s)", line));
    264                 }
    265 
    266                 if ((code >= 200) && (code < 600)) {
    267                     complete = true;
    268                 }
    269                 response.add(line);
    270             } catch (InterruptedException ex) {
    271                 Slog.e(TAG, "Failed to process response", ex);
    272             }
    273         }
    274 
    275         if (code >= ResponseCode.FailedRangeStart &&
    276                 code <= ResponseCode.FailedRangeEnd) {
    277             /*
    278              * Note: The format of the last response in this case is
    279              *        "NNN <errmsg>"
    280              */
    281             throw new NativeDaemonConnectorException(
    282                     code, cmd, response.get(response.size()-1).substring(4));
    283         }
    284         return response;
    285     }
    286 
    287     /**
    288      * Issues a list command and returns the cooked list
    289      */
    290     public String[] doListCommand(String cmd, int expectedResponseCode)
    291             throws NativeDaemonConnectorException {
    292 
    293         ArrayList<String> rsp = doCommand(cmd);
    294         String[] rdata = new String[rsp.size()-1];
    295         int idx = 0;
    296 
    297         for (int i = 0; i < rsp.size(); i++) {
    298             String line = rsp.get(i);
    299             try {
    300                 String[] tok = line.split(" ");
    301                 int code = Integer.parseInt(tok[0]);
    302                 if (code == expectedResponseCode) {
    303                     rdata[idx++] = line.substring(tok[0].length() + 1);
    304                 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
    305                     if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
    306                     int last = rsp.size() -1;
    307                     if (i != last) {
    308                         Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
    309                         for (int j = i; j <= last ; j++) {
    310                             Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
    311                         }
    312                     }
    313                     return rdata;
    314                 } else {
    315                     throw new NativeDaemonConnectorException(
    316                             String.format("Expected list response %d, but got %d",
    317                                     expectedResponseCode, code));
    318                 }
    319             } catch (NumberFormatException nfe) {
    320                 throw new NativeDaemonConnectorException(
    321                         String.format("Error reading code '%s'", line));
    322             }
    323         }
    324         throw new NativeDaemonConnectorException("Got an empty response");
    325     }
    326 
    327     /** {@inheritDoc} */
    328     public void monitor() {
    329         synchronized (mDaemonLock) { }
    330     }
    331 }
    332