• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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