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
17package com.android.internal.os;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.os.Binder;
22import android.os.IBinder;
23import android.os.IInterface;
24import android.os.ParcelFileDescriptor;
25import android.os.RemoteException;
26import android.os.SystemClock;
27import android.util.Slog;
28
29import libcore.io.IoUtils;
30
31import java.io.ByteArrayOutputStream;
32import java.io.Closeable;
33import java.io.FileDescriptor;
34import java.io.FileInputStream;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.OutputStream;
38
39/**
40 * Helper for transferring data through a pipe from a client app.
41 */
42public 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 {
59        void go(IInterface iface, FileDescriptor fd, String prefix,
60                String[] args) throws RemoteException;
61    }
62
63    public TransferPipe() throws IOException {
64        this(null);
65    }
66
67    public TransferPipe(String bufferPrefix) throws IOException {
68        this(bufferPrefix, "TransferPipe");
69    }
70
71    protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
72        mThread = new Thread(this, threadName);
73        mFds = ParcelFileDescriptor.createPipe();
74        mBufferPrefix = bufferPrefix;
75    }
76
77    ParcelFileDescriptor getReadFd() {
78        return mFds[0];
79    }
80
81    public ParcelFileDescriptor getWriteFd() {
82        return mFds[1];
83    }
84
85    public void setBufferPrefix(String prefix) {
86        mBufferPrefix = prefix;
87    }
88
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     */
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
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
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
155    static void goDump(IBinder binder, FileDescriptor out,
156            String[] args) throws IOException, RemoteException {
157        goDump(binder, out, args, DEFAULT_TIMEOUT);
158    }
159
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
177    public void go(FileDescriptor out) throws IOException {
178        go(out, DEFAULT_TIMEOUT);
179    }
180
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
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
231    public void close() {
232        kill();
233    }
234
235    public void kill() {
236        synchronized (this) {
237            closeFd(0);
238            closeFd(1);
239        }
240    }
241
242    protected OutputStream getNewOutputStream() {
243          return new FileOutputStream(mOutFd);
244    }
245
246    @Override
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