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 java.io.FileDescriptor;
20import java.io.FileInputStream;
21import java.io.FileOutputStream;
22import java.io.IOException;
23
24import android.os.Binder;
25import android.os.IBinder;
26import android.os.IInterface;
27import android.os.ParcelFileDescriptor;
28import android.os.RemoteException;
29import android.os.SystemClock;
30import android.util.Slog;
31
32/**
33 * Helper for transferring data through a pipe from a client app.
34 */
35public final class TransferPipe implements Runnable {
36    static final String TAG = "TransferPipe";
37    static final boolean DEBUG = false;
38
39    static final long DEFAULT_TIMEOUT = 5000;  // 5 seconds
40
41    final Thread mThread;;
42    final ParcelFileDescriptor[] mFds;
43
44    FileDescriptor mOutFd;
45    long mEndTime;
46    String mFailure;
47    boolean mComplete;
48
49    String mBufferPrefix;
50
51    interface Caller {
52        void go(IInterface iface, FileDescriptor fd, String prefix,
53                String[] args) throws RemoteException;
54    }
55
56    public TransferPipe() throws IOException {
57        mThread = new Thread(this, "TransferPipe");
58        mFds = ParcelFileDescriptor.createPipe();
59    }
60
61    ParcelFileDescriptor getReadFd() {
62        return mFds[0];
63    }
64
65    public ParcelFileDescriptor getWriteFd() {
66        return mFds[1];
67    }
68
69    public void setBufferPrefix(String prefix) {
70        mBufferPrefix = prefix;
71    }
72
73    static void go(Caller caller, IInterface iface, FileDescriptor out,
74            String prefix, String[] args) throws IOException, RemoteException {
75        go(caller, iface, out, prefix, args, DEFAULT_TIMEOUT);
76    }
77
78    static void go(Caller caller, IInterface iface, FileDescriptor out,
79            String prefix, String[] args, long timeout) throws IOException, RemoteException {
80        if ((iface.asBinder()) instanceof Binder) {
81            // This is a local object...  just call it directly.
82            try {
83                caller.go(iface, out, prefix, args);
84            } catch (RemoteException e) {
85            }
86            return;
87        }
88
89        TransferPipe tp = new TransferPipe();
90        try {
91            caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args);
92            tp.go(out, timeout);
93        } finally {
94            tp.kill();
95        }
96    }
97
98    static void goDump(IBinder binder, FileDescriptor out,
99            String[] args) throws IOException, RemoteException {
100        goDump(binder, out, args, DEFAULT_TIMEOUT);
101    }
102
103    static void goDump(IBinder binder, FileDescriptor out,
104            String[] args, long timeout) throws IOException, RemoteException {
105        if (binder instanceof Binder) {
106            // This is a local object...  just call it directly.
107            try {
108                binder.dump(out, args);
109            } catch (RemoteException e) {
110            }
111            return;
112        }
113
114        TransferPipe tp = new TransferPipe();
115        try {
116            binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
117            tp.go(out, timeout);
118        } finally {
119            tp.kill();
120        }
121    }
122
123    public void go(FileDescriptor out) throws IOException {
124        go(out, DEFAULT_TIMEOUT);
125    }
126
127    public void go(FileDescriptor out, long timeout) throws IOException {
128        try {
129            synchronized (this) {
130                mOutFd = out;
131                mEndTime = SystemClock.uptimeMillis() + timeout;
132
133                if (DEBUG) Slog.i(TAG, "read=" + getReadFd() + " write=" + getWriteFd()
134                        + " out=" + out);
135
136                // Close the write fd, so we know when the other side is done.
137                closeFd(1);
138
139                mThread.start();
140
141                while (mFailure == null && !mComplete) {
142                    long waitTime = mEndTime - SystemClock.uptimeMillis();
143                    if (waitTime <= 0) {
144                        if (DEBUG) Slog.i(TAG, "TIMEOUT!");
145                        mThread.interrupt();
146                        throw new IOException("Timeout");
147                    }
148
149                    try {
150                        wait(waitTime);
151                    } catch (InterruptedException e) {
152                    }
153                }
154
155                if (DEBUG) Slog.i(TAG, "Finished: " + mFailure);
156                if (mFailure != null) {
157                    throw new IOException(mFailure);
158                }
159            }
160        } finally {
161            kill();
162        }
163    }
164
165    void closeFd(int num) {
166        if (mFds[num] != null) {
167            if (DEBUG) Slog.i(TAG, "Closing: " + mFds[num]);
168            try {
169                mFds[num].close();
170            } catch (IOException e) {
171            }
172            mFds[num] = null;
173        }
174    }
175
176    public void kill() {
177        synchronized (this) {
178            closeFd(0);
179            closeFd(1);
180        }
181    }
182
183    @Override
184    public void run() {
185        final byte[] buffer = new byte[1024];
186        final FileInputStream fis;
187        final FileOutputStream fos;
188
189        synchronized (this) {
190            ParcelFileDescriptor readFd = getReadFd();
191            if (readFd == null) {
192                Slog.w(TAG, "Pipe has been closed...");
193                return;
194            }
195            fis = new FileInputStream(readFd.getFileDescriptor());
196            fos = new FileOutputStream(mOutFd);
197        }
198
199        if (DEBUG) Slog.i(TAG, "Ready to read pipe...");
200        byte[] bufferPrefix = null;
201        boolean needPrefix = true;
202        if (mBufferPrefix != null) {
203            bufferPrefix = mBufferPrefix.getBytes();
204        }
205
206        int size;
207        try {
208            while ((size=fis.read(buffer)) > 0) {
209                if (DEBUG) Slog.i(TAG, "Got " + size + " bytes");
210                if (bufferPrefix == null) {
211                    fos.write(buffer, 0, size);
212                } else {
213                    int start = 0;
214                    for (int i=0; i<size; i++) {
215                        if (buffer[i] != '\n') {
216                            if (i > start) {
217                                fos.write(buffer, start, i-start);
218                            }
219                            start = i;
220                            if (needPrefix) {
221                                fos.write(bufferPrefix);
222                                needPrefix = false;
223                            }
224                            do {
225                                i++;
226                            } while (i<size && buffer[i] != '\n');
227                            if (i < size) {
228                                needPrefix = true;
229                            }
230                        }
231                    }
232                    if (size > start) {
233                        fos.write(buffer, start, size-start);
234                    }
235                }
236            }
237            if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size);
238            if (mThread.isInterrupted()) {
239                if (DEBUG) Slog.i(TAG, "Interrupted!");
240            }
241        } catch (IOException e) {
242            synchronized (this) {
243                mFailure = e.toString();
244                notifyAll();
245                return;
246            }
247        }
248
249        synchronized (this) {
250            mComplete = true;
251            notifyAll();
252        }
253    }
254}
255