1 /* 2 * Copyright (C) 2019 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.am; 18 19 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; 20 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; 21 22 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 23 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 24 25 import android.net.LocalSocket; 26 import android.net.LocalSocketAddress; 27 import android.os.MessageQueue; 28 import android.util.Slog; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import libcore.io.IoUtils; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.DataInputStream; 36 import java.io.FileDescriptor; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.nio.ByteBuffer; 41 42 /** 43 * Lmkd connection to communicate with lowmemorykiller daemon. 44 */ 45 public class LmkdConnection { 46 private static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdConnection" : TAG_AM; 47 48 /** 49 * Max LMKD reply packet length in bytes 50 * Used to hold the data for the statsd atoms logging 51 * Must be in sync with statslog.h 52 */ 53 private static final int LMKD_REPLY_MAX_SIZE = 222; 54 55 // connection listener interface 56 interface LmkdConnectionListener { onConnect(OutputStream ostream)57 public boolean onConnect(OutputStream ostream); onDisconnect()58 public void onDisconnect(); 59 /** 60 * Check if received reply was expected (reply to an earlier request) 61 * 62 * @param replyBuf The buffer provided in exchange() to receive the reply. 63 * It can be used by exchange() caller to store reply-specific 64 * tags for later use in isReplyExpected() to verify if 65 * received packet is the expected reply. 66 * @param dataReceived The buffer holding received data 67 * @param receivedLen Size of the data received 68 */ isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived, int receivedLen)69 public boolean isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived, 70 int receivedLen); 71 72 /** 73 * Handle the received message if it's unsolicited. 74 * 75 * @param dataReceived The buffer holding received data 76 * @param receivedLen Size of the data received 77 * @return True if the message has been handled correctly, false otherwise. 78 */ handleUnsolicitedMessage(DataInputStream inputData, int receivedLen)79 boolean handleUnsolicitedMessage(DataInputStream inputData, int receivedLen); 80 } 81 82 private final MessageQueue mMsgQueue; 83 84 // lmkd connection listener 85 private final LmkdConnectionListener mListener; 86 87 // mutex to synchronize access to the socket 88 private final Object mLmkdSocketLock = new Object(); 89 90 // socket to communicate with lmkd 91 @GuardedBy("mLmkdSocketLock") 92 private LocalSocket mLmkdSocket = null; 93 94 // socket I/O streams 95 @GuardedBy("mLmkdSocketLock") 96 private OutputStream mLmkdOutputStream = null; 97 @GuardedBy("mLmkdSocketLock") 98 private InputStream mLmkdInputStream = null; 99 100 // buffer to store incoming data 101 private final ByteBuffer mInputBuf = 102 ByteBuffer.allocate(LMKD_REPLY_MAX_SIZE); 103 104 // Input stream to parse the incoming data 105 private final DataInputStream mInputData = new DataInputStream( 106 new ByteArrayInputStream(mInputBuf.array())); 107 108 // object to protect mReplyBuf and to wait/notify when reply is received 109 private final Object mReplyBufLock = new Object(); 110 111 // reply buffer 112 @GuardedBy("mReplyBufLock") 113 private ByteBuffer mReplyBuf = null; 114 115 //////////////////// END FIELDS //////////////////// 116 LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener)117 LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener) { 118 mMsgQueue = msgQueue; 119 mListener = listener; 120 } 121 connect()122 public boolean connect() { 123 synchronized (mLmkdSocketLock) { 124 if (mLmkdSocket != null) { 125 return true; 126 } 127 // temporary sockets and I/O streams 128 final LocalSocket socket = openSocket(); 129 130 if (socket == null) { 131 Slog.w(TAG, "Failed to connect to lowmemorykiller, retry later"); 132 return false; 133 } 134 135 final OutputStream ostream; 136 final InputStream istream; 137 try { 138 ostream = socket.getOutputStream(); 139 istream = socket.getInputStream(); 140 } catch (IOException ex) { 141 IoUtils.closeQuietly(socket); 142 return false; 143 } 144 // execute onConnect callback 145 if (mListener != null && !mListener.onConnect(ostream)) { 146 Slog.w(TAG, "Failed to communicate with lowmemorykiller, retry later"); 147 IoUtils.closeQuietly(socket); 148 return false; 149 } 150 // connection established 151 mLmkdSocket = socket; 152 mLmkdOutputStream = ostream; 153 mLmkdInputStream = istream; 154 mMsgQueue.addOnFileDescriptorEventListener(mLmkdSocket.getFileDescriptor(), 155 EVENT_INPUT | EVENT_ERROR, 156 new MessageQueue.OnFileDescriptorEventListener() { 157 public int onFileDescriptorEvents(FileDescriptor fd, int events) { 158 return fileDescriptorEventHandler(fd, events); 159 } 160 } 161 ); 162 mLmkdSocketLock.notifyAll(); 163 } 164 return true; 165 } 166 fileDescriptorEventHandler(FileDescriptor fd, int events)167 private int fileDescriptorEventHandler(FileDescriptor fd, int events) { 168 if (mListener == null) { 169 return 0; 170 } 171 if ((events & EVENT_INPUT) != 0) { 172 processIncomingData(); 173 } 174 if ((events & EVENT_ERROR) != 0) { 175 synchronized (mLmkdSocketLock) { 176 // stop listening on this socket 177 mMsgQueue.removeOnFileDescriptorEventListener( 178 mLmkdSocket.getFileDescriptor()); 179 IoUtils.closeQuietly(mLmkdSocket); 180 mLmkdSocket = null; 181 } 182 // wake up reply waiters if any 183 synchronized (mReplyBufLock) { 184 if (mReplyBuf != null) { 185 mReplyBuf = null; 186 mReplyBufLock.notifyAll(); 187 } 188 } 189 // notify listener 190 mListener.onDisconnect(); 191 return 0; 192 } 193 return (EVENT_INPUT | EVENT_ERROR); 194 } 195 processIncomingData()196 private void processIncomingData() { 197 int len = read(mInputBuf); 198 if (len > 0) { 199 try { 200 // reset InputStream to point into mInputBuf.array() begin 201 mInputData.reset(); 202 synchronized (mReplyBufLock) { 203 if (mReplyBuf != null) { 204 if (mListener.isReplyExpected(mReplyBuf, mInputBuf, len)) { 205 // copy into reply buffer 206 mReplyBuf.put(mInputBuf.array(), 0, len); 207 mReplyBuf.rewind(); 208 // wakeup the waiting thread 209 mReplyBufLock.notifyAll(); 210 } else if (!mListener.handleUnsolicitedMessage(mInputData, len)) { 211 // received unexpected packet 212 // treat this as an error 213 mReplyBuf = null; 214 mReplyBufLock.notifyAll(); 215 Slog.e(TAG, "Received an unexpected packet from lmkd"); 216 } 217 } else if (!mListener.handleUnsolicitedMessage(mInputData, len)) { 218 // received asynchronous communication from lmkd 219 // but we don't recognize it. 220 Slog.w(TAG, "Received an unexpected packet from lmkd"); 221 } 222 } 223 } catch (IOException e) { 224 Slog.e(TAG, "Failed to parse lmkd data buffer. Size = " + len); 225 } 226 } 227 } 228 isConnected()229 public boolean isConnected() { 230 synchronized (mLmkdSocketLock) { 231 return (mLmkdSocket != null); 232 } 233 } 234 waitForConnection(long timeoutMs)235 public boolean waitForConnection(long timeoutMs) { 236 synchronized (mLmkdSocketLock) { 237 if (mLmkdSocket != null) { 238 return true; 239 } 240 try { 241 mLmkdSocketLock.wait(timeoutMs); 242 return (mLmkdSocket != null); 243 } catch (InterruptedException e) { 244 return false; 245 } 246 } 247 } 248 openSocket()249 private LocalSocket openSocket() { 250 final LocalSocket socket; 251 252 try { 253 socket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); 254 socket.connect( 255 new LocalSocketAddress("lmkd", 256 LocalSocketAddress.Namespace.RESERVED)); 257 } catch (IOException ex) { 258 Slog.e(TAG, "Connection failed: " + ex.toString()); 259 return null; 260 } 261 return socket; 262 } 263 write(ByteBuffer buf)264 private boolean write(ByteBuffer buf) { 265 synchronized (mLmkdSocketLock) { 266 try { 267 mLmkdOutputStream.write(buf.array(), 0, buf.position()); 268 } catch (IOException ex) { 269 return false; 270 } 271 return true; 272 } 273 } 274 read(ByteBuffer buf)275 private int read(ByteBuffer buf) { 276 synchronized (mLmkdSocketLock) { 277 try { 278 return mLmkdInputStream.read(buf.array(), 0, buf.array().length); 279 } catch (IOException ex) { 280 } 281 return -1; 282 } 283 } 284 285 /** 286 * Exchange a request/reply packets with lmkd 287 * 288 * @param req The buffer holding the request data to be sent 289 * @param repl The buffer to receive the reply 290 */ exchange(ByteBuffer req, ByteBuffer repl)291 public boolean exchange(ByteBuffer req, ByteBuffer repl) { 292 if (repl == null) { 293 return write(req); 294 } 295 296 boolean result = false; 297 // set reply buffer to user-defined one to fill it 298 synchronized (mReplyBufLock) { 299 mReplyBuf = repl; 300 301 if (write(req)) { 302 try { 303 // wait for the reply 304 mReplyBufLock.wait(); 305 result = (mReplyBuf != null); 306 } catch (InterruptedException ie) { 307 result = false; 308 } 309 } 310 311 // reset reply buffer 312 mReplyBuf = null; 313 } 314 return result; 315 } 316 } 317