/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import java.io.Closeable; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemClock; import android.util.Slog; /** * Helper for transferring data through a pipe from a client app. */ public final class TransferPipe implements Runnable, Closeable { static final String TAG = "TransferPipe"; static final boolean DEBUG = false; static final long DEFAULT_TIMEOUT = 5000; // 5 seconds final Thread mThread; final ParcelFileDescriptor[] mFds; FileDescriptor mOutFd; long mEndTime; String mFailure; boolean mComplete; String mBufferPrefix; interface Caller { void go(IInterface iface, FileDescriptor fd, String prefix, String[] args) throws RemoteException; } public TransferPipe() throws IOException { this(null); } public TransferPipe(String bufferPrefix) throws IOException { mThread = new Thread(this, "TransferPipe"); mFds = ParcelFileDescriptor.createPipe(); mBufferPrefix = bufferPrefix; } ParcelFileDescriptor getReadFd() { return mFds[0]; } public ParcelFileDescriptor getWriteFd() { return mFds[1]; } public void setBufferPrefix(String prefix) { mBufferPrefix = prefix; } public static void dumpAsync(IBinder binder, FileDescriptor out, String[] args) throws IOException, RemoteException { goDump(binder, out, args); } static void go(Caller caller, IInterface iface, FileDescriptor out, String prefix, String[] args) throws IOException, RemoteException { go(caller, iface, out, prefix, args, DEFAULT_TIMEOUT); } static void go(Caller caller, IInterface iface, FileDescriptor out, String prefix, String[] args, long timeout) throws IOException, RemoteException { if ((iface.asBinder()) instanceof Binder) { // This is a local object... just call it directly. try { caller.go(iface, out, prefix, args); } catch (RemoteException e) { } return; } try (TransferPipe tp = new TransferPipe()) { caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args); tp.go(out, timeout); } } static void goDump(IBinder binder, FileDescriptor out, String[] args) throws IOException, RemoteException { goDump(binder, out, args, DEFAULT_TIMEOUT); } static void goDump(IBinder binder, FileDescriptor out, String[] args, long timeout) throws IOException, RemoteException { if (binder instanceof Binder) { // This is a local object... just call it directly. try { binder.dump(out, args); } catch (RemoteException e) { } return; } try (TransferPipe tp = new TransferPipe()) { binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); tp.go(out, timeout); } } public void go(FileDescriptor out) throws IOException { go(out, DEFAULT_TIMEOUT); } public void go(FileDescriptor out, long timeout) throws IOException { try { synchronized (this) { mOutFd = out; mEndTime = SystemClock.uptimeMillis() + timeout; if (DEBUG) Slog.i(TAG, "read=" + getReadFd() + " write=" + getWriteFd() + " out=" + out); // Close the write fd, so we know when the other side is done. closeFd(1); mThread.start(); while (mFailure == null && !mComplete) { long waitTime = mEndTime - SystemClock.uptimeMillis(); if (waitTime <= 0) { if (DEBUG) Slog.i(TAG, "TIMEOUT!"); mThread.interrupt(); throw new IOException("Timeout"); } try { wait(waitTime); } catch (InterruptedException e) { } } if (DEBUG) Slog.i(TAG, "Finished: " + mFailure); if (mFailure != null) { throw new IOException(mFailure); } } } finally { kill(); } } void closeFd(int num) { if (mFds[num] != null) { if (DEBUG) Slog.i(TAG, "Closing: " + mFds[num]); try { mFds[num].close(); } catch (IOException e) { } mFds[num] = null; } } @Override public void close() { kill(); } public void kill() { synchronized (this) { closeFd(0); closeFd(1); } } @Override public void run() { final byte[] buffer = new byte[1024]; final FileInputStream fis; final FileOutputStream fos; synchronized (this) { ParcelFileDescriptor readFd = getReadFd(); if (readFd == null) { Slog.w(TAG, "Pipe has been closed..."); return; } fis = new FileInputStream(readFd.getFileDescriptor()); fos = new FileOutputStream(mOutFd); } if (DEBUG) Slog.i(TAG, "Ready to read pipe..."); byte[] bufferPrefix = null; boolean needPrefix = true; if (mBufferPrefix != null) { bufferPrefix = mBufferPrefix.getBytes(); } int size; try { while ((size=fis.read(buffer)) > 0) { if (DEBUG) Slog.i(TAG, "Got " + size + " bytes"); if (bufferPrefix == null) { fos.write(buffer, 0, size); } else { int start = 0; for (int i=0; i start) { fos.write(buffer, start, i-start); } start = i; if (needPrefix) { fos.write(bufferPrefix); needPrefix = false; } do { i++; } while (i start) { fos.write(buffer, start, size-start); } } } if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size); if (mThread.isInterrupted()) { if (DEBUG) Slog.i(TAG, "Interrupted!"); } } catch (IOException e) { synchronized (this) { mFailure = e.toString(); notifyAll(); return; } } synchronized (this) { mComplete = true; notifyAll(); } } }