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