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.server.am;
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 */
35class 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    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    ParcelFileDescriptor getWriteFd() {
66        return mFds[1];
67    }
68
69    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    void go(FileDescriptor out) throws IOException {
124        go(out, DEFAULT_TIMEOUT);
125    }
126
127    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    void kill() {
177        closeFd(0);
178        closeFd(1);
179    }
180
181    @Override
182    public void run() {
183        final byte[] buffer = new byte[1024];
184        final FileInputStream fis = new FileInputStream(getReadFd().getFileDescriptor());
185        final FileOutputStream fos = new FileOutputStream(mOutFd);
186
187        if (DEBUG) Slog.i(TAG, "Ready to read pipe...");
188        byte[] bufferPrefix = null;
189        boolean needPrefix = true;
190        if (mBufferPrefix != null) {
191            bufferPrefix = mBufferPrefix.getBytes();
192        }
193
194        int size;
195        try {
196            while ((size=fis.read(buffer)) > 0) {
197                if (DEBUG) Slog.i(TAG, "Got " + size + " bytes");
198                if (bufferPrefix == null) {
199                    fos.write(buffer, 0, size);
200                } else {
201                    int start = 0;
202                    for (int i=0; i<size; i++) {
203                        if (buffer[i] != '\n') {
204                            if (i > start) {
205                                fos.write(buffer, start, i-start);
206                            }
207                            start = i;
208                            if (needPrefix) {
209                                fos.write(bufferPrefix);
210                                needPrefix = false;
211                            }
212                            do {
213                                i++;
214                            } while (i<size && buffer[i] != '\n');
215                            if (i < size) {
216                                needPrefix = true;
217                            }
218                        }
219                    }
220                    if (size > start) {
221                        fos.write(buffer, start, size-start);
222                    }
223                }
224            }
225            if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size);
226            if (mThread.isInterrupted()) {
227                if (DEBUG) Slog.i(TAG, "Interrupted!");
228            }
229        } catch (IOException e) {
230            synchronized (this) {
231                mFailure = e.toString();
232                notifyAll();
233                return;
234            }
235        }
236
237        synchronized (this) {
238            mComplete = true;
239            notifyAll();
240        }
241    }
242}
243