blob: 1c09bd6a2a66978a55447492ef9b29e598a778b4 [file] [log] [blame]
Dianne Hackborne17aeb32011-04-07 15:11:57 -07001/*
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
Dianne Hackborncbfd23e2013-06-11 14:26:53 -070017package com.android.internal.os;
Dianne Hackborne17aeb32011-04-07 15:11:57 -070018
Philip P. Moltmannc0a128d2017-06-19 10:55:09 -070019import android.annotation.NonNull;
20import android.annotation.Nullable;
Dianne Hackborne17aeb32011-04-07 15:11:57 -070021import 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
Philip P. Moltmannc0a128d2017-06-19 10:55:09 -070029import 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;
Kweku Adams983829f2017-12-06 14:53:50 -080037import java.io.OutputStream;
Philip P. Moltmannc0a128d2017-06-19 10:55:09 -070038
Dianne Hackborne17aeb32011-04-07 15:11:57 -070039/**
40 * Helper for transferring data through a pipe from a client app.
41 */
Kweku Adams983829f2017-12-06 14:53:50 -080042public class TransferPipe implements Runnable, Closeable {
Dianne Hackborne17aeb32011-04-07 15:11:57 -070043 static final String TAG = "TransferPipe";
44 static final boolean DEBUG = false;
45
46 static final long DEFAULT_TIMEOUT = 5000; // 5 seconds
47
Jeff Sharkey850c83e2016-11-09 12:25:44 -070048 final Thread mThread;
Dianne Hackborne17aeb32011-04-07 15:11:57 -070049 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
Dianne Hackborncbfd23e2013-06-11 14:26:53 -070063 public TransferPipe() throws IOException {
Jeff Sharkey850c83e2016-11-09 12:25:44 -070064 this(null);
65 }
66
67 public TransferPipe(String bufferPrefix) throws IOException {
Kweku Adams983829f2017-12-06 14:53:50 -080068 this(bufferPrefix, "TransferPipe");
69 }
70
71 protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
72 mThread = new Thread(this, threadName);
Dianne Hackborne17aeb32011-04-07 15:11:57 -070073 mFds = ParcelFileDescriptor.createPipe();
Jeff Sharkey850c83e2016-11-09 12:25:44 -070074 mBufferPrefix = bufferPrefix;
Dianne Hackborne17aeb32011-04-07 15:11:57 -070075 }
76
77 ParcelFileDescriptor getReadFd() {
78 return mFds[0];
79 }
80
Dianne Hackborncbfd23e2013-06-11 14:26:53 -070081 public ParcelFileDescriptor getWriteFd() {
Dianne Hackborne17aeb32011-04-07 15:11:57 -070082 return mFds[1];
83 }
84
Dianne Hackborncbfd23e2013-06-11 14:26:53 -070085 public void setBufferPrefix(String prefix) {
Dianne Hackborne17aeb32011-04-07 15:11:57 -070086 mBufferPrefix = prefix;
87 }
88
Jeff Sharkey850c83e2016-11-09 12:25:44 -070089 public static void dumpAsync(IBinder binder, FileDescriptor out, String[] args)
90 throws IOException, RemoteException {
91 goDump(binder, out, args);
92 }
93
Philip P. Moltmannc0a128d2017-06-19 10:55:09 -070094 /**
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
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700133 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
Jeff Sharkey850c83e2016-11-09 12:25:44 -0700149 try (TransferPipe tp = new TransferPipe()) {
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700150 caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args);
151 tp.go(out, timeout);
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700152 }
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
Jeff Sharkey850c83e2016-11-09 12:25:44 -0700171 try (TransferPipe tp = new TransferPipe()) {
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700172 binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
173 tp.go(out, timeout);
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700174 }
175 }
176
Dianne Hackborncbfd23e2013-06-11 14:26:53 -0700177 public void go(FileDescriptor out) throws IOException {
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700178 go(out, DEFAULT_TIMEOUT);
179 }
180
Dianne Hackborncbfd23e2013-06-11 14:26:53 -0700181 public void go(FileDescriptor out, long timeout) throws IOException {
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700182 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
Jeff Sharkey850c83e2016-11-09 12:25:44 -0700230 @Override
231 public void close() {
232 kill();
233 }
234
Dianne Hackborncbfd23e2013-06-11 14:26:53 -0700235 public void kill() {
Xin Guan5ec679a2014-08-21 13:05:34 -0500236 synchronized (this) {
237 closeFd(0);
238 closeFd(1);
239 }
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700240 }
241
Kweku Adams983829f2017-12-06 14:53:50 -0800242 protected OutputStream getNewOutputStream() {
243 return new FileOutputStream(mOutFd);
244 }
245
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700246 @Override
247 public void run() {
248 final byte[] buffer = new byte[1024];
Xin Guan5ec679a2014-08-21 13:05:34 -0500249 final FileInputStream fis;
Kweku Adams983829f2017-12-06 14:53:50 -0800250 final OutputStream fos;
Xin Guan5ec679a2014-08-21 13:05:34 -0500251
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());
Kweku Adams983829f2017-12-06 14:53:50 -0800259 fos = getNewOutputStream();
Xin Guan5ec679a2014-08-21 13:05:34 -0500260 }
Dianne Hackborne17aeb32011-04-07 15:11:57 -0700261
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}