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 // mutex to synchronize socket output stream with socket creation/destruction 95 private final Object mLmkdOutputStreamLock = new Object(); 96 97 // socket output stream 98 @GuardedBy("mLmkdOutputStreamLock") 99 private OutputStream mLmkdOutputStream = null; 100 101 // mutex to synchronize socket input stream with socket creation/destruction 102 private final Object mLmkdInputStreamLock = new Object(); 103 104 // socket input stream 105 @GuardedBy("mLmkdInputStreamLock") 106 private InputStream mLmkdInputStream = null; 107 108 // buffer to store incoming data 109 private final ByteBuffer mInputBuf = 110 ByteBuffer.allocate(LMKD_REPLY_MAX_SIZE); 111 112 // Input stream to parse the incoming data 113 private final DataInputStream mInputData = new DataInputStream( 114 new ByteArrayInputStream(mInputBuf.array())); 115 116 // object to protect mReplyBuf and to wait/notify when reply is received 117 private final Object mReplyBufLock = new Object(); 118 119 // reply buffer 120 @GuardedBy("mReplyBufLock") 121 private ByteBuffer mReplyBuf = null; 122 123 //////////////////// END FIELDS //////////////////// 124 LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener)125 LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener) { 126 mMsgQueue = msgQueue; 127 mListener = listener; 128 } 129 connect()130 public boolean connect() { 131 synchronized (mLmkdSocketLock) { 132 if (mLmkdSocket != null) { 133 return true; 134 } 135 // temporary sockets and I/O streams 136 final LocalSocket socket = openSocket(); 137 138 if (socket == null) { 139 Slog.w(TAG, "Failed to connect to lowmemorykiller, retry later"); 140 return false; 141 } 142 143 final OutputStream ostream; 144 final InputStream istream; 145 try { 146 ostream = socket.getOutputStream(); 147 istream = socket.getInputStream(); 148 } catch (IOException ex) { 149 IoUtils.closeQuietly(socket); 150 return false; 151 } 152 // execute onConnect callback 153 if (mListener != null && !mListener.onConnect(ostream)) { 154 Slog.w(TAG, "Failed to communicate with lowmemorykiller, retry later"); 155 IoUtils.closeQuietly(socket); 156 return false; 157 } 158 // connection established 159 synchronized(mLmkdOutputStreamLock) { 160 synchronized(mLmkdInputStreamLock) { 161 mLmkdSocket = socket; 162 mLmkdOutputStream = ostream; 163 mLmkdInputStream = istream; 164 } 165 } 166 mMsgQueue.addOnFileDescriptorEventListener(mLmkdSocket.getFileDescriptor(), 167 EVENT_INPUT | EVENT_ERROR, 168 new MessageQueue.OnFileDescriptorEventListener() { 169 public int onFileDescriptorEvents(FileDescriptor fd, int events) { 170 return fileDescriptorEventHandler(fd, events); 171 } 172 } 173 ); 174 mLmkdSocketLock.notifyAll(); 175 } 176 return true; 177 } 178 fileDescriptorEventHandler(FileDescriptor fd, int events)179 private int fileDescriptorEventHandler(FileDescriptor fd, int events) { 180 if (mListener == null) { 181 return 0; 182 } 183 if ((events & EVENT_INPUT) != 0) { 184 processIncomingData(); 185 } 186 if ((events & EVENT_ERROR) != 0) { 187 synchronized (mLmkdSocketLock) { 188 // stop listening on this socket 189 mMsgQueue.removeOnFileDescriptorEventListener( 190 mLmkdSocket.getFileDescriptor()); 191 IoUtils.closeQuietly(mLmkdSocket); 192 synchronized(mLmkdOutputStreamLock) { 193 synchronized(mLmkdInputStreamLock) { 194 mLmkdOutputStream = null; 195 mLmkdInputStream = null; 196 mLmkdSocket = null; 197 } 198 } 199 } 200 // wake up reply waiters if any 201 synchronized (mReplyBufLock) { 202 if (mReplyBuf != null) { 203 mReplyBuf = null; 204 mReplyBufLock.notifyAll(); 205 } 206 } 207 // notify listener 208 mListener.onDisconnect(); 209 return 0; 210 } 211 return (EVENT_INPUT | EVENT_ERROR); 212 } 213 processIncomingData()214 private void processIncomingData() { 215 int len = read(mInputBuf); 216 if (len > 0) { 217 try { 218 // reset InputStream to point into mInputBuf.array() begin 219 mInputData.reset(); 220 synchronized (mReplyBufLock) { 221 if (mReplyBuf != null) { 222 if (mListener.isReplyExpected(mReplyBuf, mInputBuf, len)) { 223 // copy into reply buffer 224 mReplyBuf.put(mInputBuf.array(), 0, len); 225 mReplyBuf.rewind(); 226 // wakeup the waiting thread 227 mReplyBufLock.notifyAll(); 228 } else if (!mListener.handleUnsolicitedMessage(mInputData, len)) { 229 // received unexpected packet 230 // treat this as an error 231 mReplyBuf = null; 232 mReplyBufLock.notifyAll(); 233 Slog.e(TAG, "Received an unexpected packet from lmkd"); 234 } 235 } else if (!mListener.handleUnsolicitedMessage(mInputData, len)) { 236 // received asynchronous communication from lmkd 237 // but we don't recognize it. 238 Slog.w(TAG, "Received an unexpected packet from lmkd"); 239 } 240 } 241 } catch (IOException e) { 242 Slog.e(TAG, "Failed to parse lmkd data buffer. Size = " + len); 243 } 244 } 245 } 246 isConnected()247 public boolean isConnected() { 248 synchronized (mLmkdSocketLock) { 249 return (mLmkdSocket != null); 250 } 251 } 252 waitForConnection(long timeoutMs)253 public boolean waitForConnection(long timeoutMs) { 254 synchronized (mLmkdSocketLock) { 255 if (mLmkdSocket != null) { 256 return true; 257 } 258 try { 259 mLmkdSocketLock.wait(timeoutMs); 260 return (mLmkdSocket != null); 261 } catch (InterruptedException e) { 262 return false; 263 } 264 } 265 } 266 openSocket()267 private LocalSocket openSocket() { 268 final LocalSocket socket; 269 270 try { 271 socket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); 272 socket.connect( 273 new LocalSocketAddress("lmkd", 274 LocalSocketAddress.Namespace.RESERVED)); 275 } catch (IOException ex) { 276 Slog.e(TAG, "Connection failed: " + ex.toString()); 277 return null; 278 } 279 return socket; 280 } 281 write(ByteBuffer buf)282 private boolean write(ByteBuffer buf) { 283 boolean result = false; 284 285 synchronized(mLmkdOutputStreamLock) { 286 if (mLmkdOutputStream != null) { 287 try { 288 mLmkdOutputStream.write(buf.array(), 0, buf.position()); 289 result = true; 290 } catch (IOException ex) { 291 } 292 } 293 } 294 295 return result; 296 } 297 read(ByteBuffer buf)298 private int read(ByteBuffer buf) { 299 int result = -1; 300 301 synchronized(mLmkdInputStreamLock) { 302 if (mLmkdInputStream != null) { 303 try { 304 result = mLmkdInputStream.read(buf.array(), 0, buf.array().length); 305 } catch (IOException ex) { 306 } 307 } 308 } 309 return result; 310 } 311 312 /** 313 * Exchange a request/reply packets with lmkd 314 * 315 * @param req The buffer holding the request data to be sent 316 * @param repl The buffer to receive the reply 317 */ exchange(ByteBuffer req, ByteBuffer repl)318 public boolean exchange(ByteBuffer req, ByteBuffer repl) { 319 if (repl == null) { 320 return write(req); 321 } 322 323 boolean result = false; 324 // set reply buffer to user-defined one to fill it 325 synchronized (mReplyBufLock) { 326 mReplyBuf = repl; 327 328 if (write(req)) { 329 try { 330 // wait for the reply 331 mReplyBufLock.wait(); 332 result = (mReplyBuf != null); 333 } catch (InterruptedException ie) { 334 result = false; 335 } 336 } 337 338 // reset reply buffer 339 mReplyBuf = null; 340 } 341 return result; 342 } 343 } 344