318 lines
9.8 KiB
Java
318 lines
9.8 KiB
Java
/*
|
|
* 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 android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
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;
|
|
|
|
import libcore.io.IoUtils;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.Closeable;
|
|
import java.io.FileDescriptor;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
|
|
/**
|
|
* Helper for transferring data through a pipe from a client app.
|
|
*/
|
|
public 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 {
|
|
this(bufferPrefix, "TransferPipe");
|
|
}
|
|
|
|
protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
|
|
mThread = new Thread(this, threadName);
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Read raw bytes from a service's dump function.
|
|
*
|
|
* <p>This can be used for dumping {@link android.util.proto.ProtoOutputStream protos}.
|
|
*
|
|
* @param binder The service providing the data
|
|
* @param args The arguments passed to the dump function of the service
|
|
*/
|
|
public static byte[] dumpAsync(@NonNull IBinder binder, @Nullable String... args)
|
|
throws IOException, RemoteException {
|
|
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
|
try {
|
|
TransferPipe.dumpAsync(binder, pipe[1].getFileDescriptor(), args);
|
|
|
|
// Data is written completely when dumpAsync is done
|
|
pipe[1].close();
|
|
pipe[1] = null;
|
|
|
|
byte[] buffer = new byte[4096];
|
|
try (ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream()) {
|
|
try (FileInputStream is = new FileInputStream(pipe[0].getFileDescriptor())) {
|
|
while (true) {
|
|
int numRead = is.read(buffer);
|
|
if (numRead == -1) {
|
|
break;
|
|
}
|
|
|
|
combinedBuffer.write(buffer, 0, numRead);
|
|
}
|
|
}
|
|
|
|
return combinedBuffer.toByteArray();
|
|
}
|
|
} finally {
|
|
pipe[0].close();
|
|
IoUtils.closeQuietly(pipe[1]);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
protected OutputStream getNewOutputStream() {
|
|
return new FileOutputStream(mOutFd);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
final byte[] buffer = new byte[1024];
|
|
final FileInputStream fis;
|
|
final OutputStream fos;
|
|
|
|
synchronized (this) {
|
|
ParcelFileDescriptor readFd = getReadFd();
|
|
if (readFd == null) {
|
|
Slog.w(TAG, "Pipe has been closed...");
|
|
return;
|
|
}
|
|
fis = new FileInputStream(readFd.getFileDescriptor());
|
|
fos = getNewOutputStream();
|
|
}
|
|
|
|
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<size; i++) {
|
|
if (buffer[i] != '\n') {
|
|
if (i > start) {
|
|
fos.write(buffer, start, i-start);
|
|
}
|
|
start = i;
|
|
if (needPrefix) {
|
|
fos.write(bufferPrefix);
|
|
needPrefix = false;
|
|
}
|
|
do {
|
|
i++;
|
|
} while (i<size && buffer[i] != '\n');
|
|
if (i < size) {
|
|
needPrefix = true;
|
|
}
|
|
}
|
|
}
|
|
if (size > 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();
|
|
}
|
|
}
|
|
}
|