1 /*
2  * Copyright (C) 2011 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.internal.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Binder;
22 import android.os.IBinder;
23 import android.os.IInterface;
24 import android.os.ParcelFileDescriptor;
25 import android.os.RemoteException;
26 import android.os.SystemClock;
27 import android.util.Slog;
28 
29 import libcore.io.IoUtils;
30 
31 import java.io.ByteArrayOutputStream;
32 import java.io.Closeable;
33 import java.io.FileDescriptor;
34 import java.io.FileInputStream;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.OutputStream;
38 
39 /**
40  * Helper for transferring data through a pipe from a client app.
41  */
42 public class TransferPipe implements Runnable, Closeable {
43     static final String TAG = "TransferPipe";
44     static final boolean DEBUG = false;
45 
46     static final long DEFAULT_TIMEOUT = 5000;  // 5 seconds
47 
48     final Thread mThread;
49     final ParcelFileDescriptor[] mFds;
50 
51     FileDescriptor mOutFd;
52     long mEndTime;
53     String mFailure;
54     boolean mComplete;
55 
56     String mBufferPrefix;
57 
58     interface Caller {
go(IInterface iface, FileDescriptor fd, String prefix, String[] args)59         void go(IInterface iface, FileDescriptor fd, String prefix,
60                 String[] args) throws RemoteException;
61     }
62 
TransferPipe()63     public TransferPipe() throws IOException {
64         this(null);
65     }
66 
TransferPipe(String bufferPrefix)67     public TransferPipe(String bufferPrefix) throws IOException {
68         this(bufferPrefix, "TransferPipe");
69     }
70 
TransferPipe(String bufferPrefix, String threadName)71     protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
72         mThread = new Thread(this, threadName);
73         mFds = ParcelFileDescriptor.createPipe();
74         mBufferPrefix = bufferPrefix;
75     }
76 
getReadFd()77     ParcelFileDescriptor getReadFd() {
78         return mFds[0];
79     }
80 
getWriteFd()81     public ParcelFileDescriptor getWriteFd() {
82         return mFds[1];
83     }
84 
setBufferPrefix(String prefix)85     public void setBufferPrefix(String prefix) {
86         mBufferPrefix = prefix;
87     }
88 
dumpAsync(IBinder binder, FileDescriptor out, String[] args)89     public static void dumpAsync(IBinder binder, FileDescriptor out, String[] args)
90             throws IOException, RemoteException {
91         goDump(binder, out, args);
92     }
93 
94     /**
95      * Read raw bytes from a service's dump function.
96      *
97      * <p>This can be used for dumping {@link android.util.proto.ProtoOutputStream protos}.
98      *
99      * @param binder The service providing the data
100      * @param args The arguments passed to the dump function of the service
101      */
dumpAsync(@onNull IBinder binder, @Nullable String... args)102     public static byte[] dumpAsync(@NonNull IBinder binder, @Nullable String... args)
103             throws IOException, RemoteException {
104         ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
105         try {
106             TransferPipe.dumpAsync(binder, pipe[1].getFileDescriptor(), args);
107 
108             // Data is written completely when dumpAsync is done
109             pipe[1].close();
110             pipe[1] = null;
111 
112             byte[] buffer = new byte[4096];
113             try (ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream()) {
114                 try (FileInputStream is = new FileInputStream(pipe[0].getFileDescriptor())) {
115                     while (true) {
116                         int numRead = is.read(buffer);
117                         if (numRead == -1) {
118                             break;
119                         }
120 
121                         combinedBuffer.write(buffer, 0, numRead);
122                     }
123                 }
124 
125                 return combinedBuffer.toByteArray();
126             }
127         } finally {
128             pipe[0].close();
129             IoUtils.closeQuietly(pipe[1]);
130         }
131     }
132 
go(Caller caller, IInterface iface, FileDescriptor out, String prefix, String[] args)133     static void go(Caller caller, IInterface iface, FileDescriptor out,
134             String prefix, String[] args) throws IOException, RemoteException {
135         go(caller, iface, out, prefix, args, DEFAULT_TIMEOUT);
136     }
137 
go(Caller caller, IInterface iface, FileDescriptor out, String prefix, String[] args, long timeout)138     static void go(Caller caller, IInterface iface, FileDescriptor out,
139             String prefix, String[] args, long timeout) throws IOException, RemoteException {
140         if ((iface.asBinder()) instanceof Binder) {
141             // This is a local object...  just call it directly.
142             try {
143                 caller.go(iface, out, prefix, args);
144             } catch (RemoteException e) {
145             }
146             return;
147         }
148 
149         try (TransferPipe tp = new TransferPipe()) {
150             caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args);
151             tp.go(out, timeout);
152         }
153     }
154 
goDump(IBinder binder, FileDescriptor out, String[] args)155     static void goDump(IBinder binder, FileDescriptor out,
156             String[] args) throws IOException, RemoteException {
157         goDump(binder, out, args, DEFAULT_TIMEOUT);
158     }
159 
goDump(IBinder binder, FileDescriptor out, String[] args, long timeout)160     static void goDump(IBinder binder, FileDescriptor out,
161             String[] args, long timeout) throws IOException, RemoteException {
162         if (binder instanceof Binder) {
163             // This is a local object...  just call it directly.
164             try {
165                 binder.dump(out, args);
166             } catch (RemoteException e) {
167             }
168             return;
169         }
170 
171         try (TransferPipe tp = new TransferPipe()) {
172             binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
173             tp.go(out, timeout);
174         }
175     }
176 
go(FileDescriptor out)177     public void go(FileDescriptor out) throws IOException {
178         go(out, DEFAULT_TIMEOUT);
179     }
180 
go(FileDescriptor out, long timeout)181     public void go(FileDescriptor out, long timeout) throws IOException {
182         try {
183             synchronized (this) {
184                 mOutFd = out;
185                 mEndTime = SystemClock.uptimeMillis() + timeout;
186 
187                 if (DEBUG) Slog.i(TAG, "read=" + getReadFd() + " write=" + getWriteFd()
188                         + " out=" + out);
189 
190                 // Close the write fd, so we know when the other side is done.
191                 closeFd(1);
192 
193                 mThread.start();
194 
195                 while (mFailure == null && !mComplete) {
196                     long waitTime = mEndTime - SystemClock.uptimeMillis();
197                     if (waitTime <= 0) {
198                         if (DEBUG) Slog.i(TAG, "TIMEOUT!");
199                         mThread.interrupt();
200                         throw new IOException("Timeout");
201                     }
202 
203                     try {
204                         wait(waitTime);
205                     } catch (InterruptedException e) {
206                     }
207                 }
208 
209                 if (DEBUG) Slog.i(TAG, "Finished: " + mFailure);
210                 if (mFailure != null) {
211                     throw new IOException(mFailure);
212                 }
213             }
214         } finally {
215             kill();
216         }
217     }
218 
closeFd(int num)219     void closeFd(int num) {
220         if (mFds[num] != null) {
221             if (DEBUG) Slog.i(TAG, "Closing: " + mFds[num]);
222             try {
223                 mFds[num].close();
224             } catch (IOException e) {
225             }
226             mFds[num] = null;
227         }
228     }
229 
230     @Override
close()231     public void close() {
232         kill();
233     }
234 
kill()235     public void kill() {
236         synchronized (this) {
237             closeFd(0);
238             closeFd(1);
239         }
240     }
241 
getNewOutputStream()242     protected OutputStream getNewOutputStream() {
243           return new FileOutputStream(mOutFd);
244     }
245 
246     @Override
run()247     public void run() {
248         final byte[] buffer = new byte[1024];
249         final FileInputStream fis;
250         final OutputStream fos;
251 
252         synchronized (this) {
253             ParcelFileDescriptor readFd = getReadFd();
254             if (readFd == null) {
255                 Slog.w(TAG, "Pipe has been closed...");
256                 return;
257             }
258             fis = new FileInputStream(readFd.getFileDescriptor());
259             fos = getNewOutputStream();
260         }
261 
262         if (DEBUG) Slog.i(TAG, "Ready to read pipe...");
263         byte[] bufferPrefix = null;
264         boolean needPrefix = true;
265         if (mBufferPrefix != null) {
266             bufferPrefix = mBufferPrefix.getBytes();
267         }
268 
269         int size;
270         try {
271             while ((size=fis.read(buffer)) > 0) {
272                 if (DEBUG) Slog.i(TAG, "Got " + size + " bytes");
273                 if (bufferPrefix == null) {
274                     fos.write(buffer, 0, size);
275                 } else {
276                     int start = 0;
277                     for (int i=0; i<size; i++) {
278                         if (buffer[i] != '\n') {
279                             if (i > start) {
280                                 fos.write(buffer, start, i-start);
281                             }
282                             start = i;
283                             if (needPrefix) {
284                                 fos.write(bufferPrefix);
285                                 needPrefix = false;
286                             }
287                             do {
288                                 i++;
289                             } while (i<size && buffer[i] != '\n');
290                             if (i < size) {
291                                 needPrefix = true;
292                             }
293                         }
294                     }
295                     if (size > start) {
296                         fos.write(buffer, start, size-start);
297                     }
298                 }
299             }
300             if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size);
301             if (mThread.isInterrupted()) {
302                 if (DEBUG) Slog.i(TAG, "Interrupted!");
303             }
304         } catch (IOException e) {
305             synchronized (this) {
306                 mFailure = e.toString();
307                 notifyAll();
308                 return;
309             }
310         }
311 
312         synchronized (this) {
313             mComplete = true;
314             notifyAll();
315         }
316     }
317 }
318