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.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