blob: fa05a5e1b22e47a84d8ba504fa1ee967b8c3dd6a [file] [log] [blame]
Justin Klaassen10d07c82017-09-15 17:58:39 -04001/*
2 * Copyright (C) 2015 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 android.os;
18
19import android.util.Slog;
Justin Klaassen46c77c22017-10-30 17:25:37 -040020
Justin Klaassen10d07c82017-09-15 17:58:39 -040021import com.android.internal.util.FastPrintWriter;
22
23import java.io.BufferedInputStream;
24import java.io.FileDescriptor;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.io.PrintWriter;
30
31/**
32 * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
33 * @hide
34 */
35public abstract class ShellCommand {
36 static final String TAG = "ShellCommand";
37 static final boolean DEBUG = false;
38
39 private Binder mTarget;
40 private FileDescriptor mIn;
41 private FileDescriptor mOut;
42 private FileDescriptor mErr;
43 private String[] mArgs;
44 private ShellCallback mShellCallback;
45 private ResultReceiver mResultReceiver;
46
47 private String mCmd;
48 private int mArgPos;
49 private String mCurArgData;
50
51 private FileInputStream mFileIn;
52 private FileOutputStream mFileOut;
53 private FileOutputStream mFileErr;
54
55 private FastPrintWriter mOutPrintWriter;
56 private FastPrintWriter mErrPrintWriter;
57 private InputStream mInputStream;
58
59 public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
60 String[] args, ShellCallback callback, int firstArgPos) {
61 mTarget = target;
62 mIn = in;
63 mOut = out;
64 mErr = err;
65 mArgs = args;
66 mShellCallback = callback;
67 mResultReceiver = null;
68 mCmd = null;
69 mArgPos = firstArgPos;
70 mCurArgData = null;
71 mFileIn = null;
72 mFileOut = null;
73 mFileErr = null;
74 mOutPrintWriter = null;
75 mErrPrintWriter = null;
76 mInputStream = null;
77 }
78
79 public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
80 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
81 String cmd;
82 int start;
83 if (args != null && args.length > 0) {
84 cmd = args[0];
85 start = 1;
86 } else {
87 cmd = null;
88 start = 0;
89 }
90 init(target, in, out, err, args, callback, start);
91 mCmd = cmd;
92 mResultReceiver = resultReceiver;
93
Justin Klaassen4217cf82017-11-30 18:18:21 -050094 if (DEBUG) {
95 RuntimeException here = new RuntimeException("here");
96 here.fillInStackTrace();
97 Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
98 Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
99 + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
100 }
Justin Klaassen10d07c82017-09-15 17:58:39 -0400101 int res = -1;
102 try {
103 res = onCommand(mCmd);
104 if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
105 } catch (SecurityException e) {
106 PrintWriter eout = getErrPrintWriter();
107 eout.println("Security exception: " + e.getMessage());
108 eout.println();
109 e.printStackTrace(eout);
110 } catch (Throwable e) {
111 // Unlike usual calls, in this case if an exception gets thrown
112 // back to us we want to print it back in to the dump data, since
113 // that is where the caller expects all interesting information to
114 // go.
115 PrintWriter eout = getErrPrintWriter();
116 eout.println();
117 eout.println("Exception occurred while executing:");
118 e.printStackTrace(eout);
119 } finally {
120 if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
121 if (mOutPrintWriter != null) {
122 mOutPrintWriter.flush();
123 }
124 if (mErrPrintWriter != null) {
125 mErrPrintWriter.flush();
126 }
127 if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
Justin Klaassen46c77c22017-10-30 17:25:37 -0400128 if (mResultReceiver != null) {
129 mResultReceiver.send(res, null);
130 }
Justin Klaassen10d07c82017-09-15 17:58:39 -0400131 }
132 if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
133 return res;
134 }
135
136 /**
Justin Klaassen46c77c22017-10-30 17:25:37 -0400137 * Adopt the ResultReceiver that was given to this shell command from it, taking
138 * it over. Primarily used to dispatch to another shell command. Once called,
139 * this shell command will no longer return its own result when done.
140 */
141 public ResultReceiver adoptResultReceiver() {
142 ResultReceiver rr = mResultReceiver;
143 mResultReceiver = null;
144 return rr;
145 }
146
147 /**
148 * Return the raw FileDescriptor for the output stream.
149 */
150 public FileDescriptor getOutFileDescriptor() {
151 return mOut;
152 }
153
154 /**
Justin Klaassen10d07c82017-09-15 17:58:39 -0400155 * Return direct raw access (not buffered) to the command's output data stream.
156 */
157 public OutputStream getRawOutputStream() {
158 if (mFileOut == null) {
159 mFileOut = new FileOutputStream(mOut);
160 }
161 return mFileOut;
162 }
163
164 /**
165 * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
166 */
167 public PrintWriter getOutPrintWriter() {
168 if (mOutPrintWriter == null) {
169 mOutPrintWriter = new FastPrintWriter(getRawOutputStream());
170 }
171 return mOutPrintWriter;
172 }
173
174 /**
Justin Klaassen46c77c22017-10-30 17:25:37 -0400175 * Return the raw FileDescriptor for the error stream.
176 */
177 public FileDescriptor getErrFileDescriptor() {
178 return mErr;
179 }
180
181 /**
Justin Klaassen10d07c82017-09-15 17:58:39 -0400182 * Return direct raw access (not buffered) to the command's error output data stream.
183 */
184 public OutputStream getRawErrorStream() {
185 if (mFileErr == null) {
186 mFileErr = new FileOutputStream(mErr);
187 }
188 return mFileErr;
189 }
190
191 /**
192 * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
193 */
194 public PrintWriter getErrPrintWriter() {
195 if (mErr == null) {
196 return getOutPrintWriter();
197 }
198 if (mErrPrintWriter == null) {
199 mErrPrintWriter = new FastPrintWriter(getRawErrorStream());
200 }
201 return mErrPrintWriter;
202 }
203
204 /**
Justin Klaassen46c77c22017-10-30 17:25:37 -0400205 * Return the raw FileDescriptor for the input stream.
206 */
207 public FileDescriptor getInFileDescriptor() {
208 return mIn;
209 }
210
211 /**
Justin Klaassen10d07c82017-09-15 17:58:39 -0400212 * Return direct raw access (not buffered) to the command's input data stream.
213 */
214 public InputStream getRawInputStream() {
215 if (mFileIn == null) {
216 mFileIn = new FileInputStream(mIn);
217 }
218 return mFileIn;
219 }
220
221 /**
222 * Return buffered access to the command's {@link #getRawInputStream()}.
223 */
224 public InputStream getBufferedInputStream() {
225 if (mInputStream == null) {
226 mInputStream = new BufferedInputStream(getRawInputStream());
227 }
228 return mInputStream;
229 }
230
231 /**
232 * Helper for just system services to ask the shell to open an output file.
233 * @hide
234 */
Justin Klaassen6a65f2d2017-11-17 16:38:15 -0500235 public ParcelFileDescriptor openFileForSystem(String path, String mode) {
Justin Klaassen4217cf82017-11-30 18:18:21 -0500236 if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400237 try {
Justin Klaassen6a65f2d2017-11-17 16:38:15 -0500238 ParcelFileDescriptor pfd = getShellCallback().openFile(path,
239 "u:r:system_server:s0", mode);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400240 if (pfd != null) {
Justin Klaassen4217cf82017-11-30 18:18:21 -0500241 if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400242 return pfd;
243 }
244 } catch (RuntimeException e) {
Justin Klaassen4217cf82017-11-30 18:18:21 -0500245 if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
Justin Klaassen10d07c82017-09-15 17:58:39 -0400246 getErrPrintWriter().println("Failure opening file: " + e.getMessage());
247 }
Justin Klaassen4217cf82017-11-30 18:18:21 -0500248 if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400249 getErrPrintWriter().println("Error: Unable to open file: " + path);
250 getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
251 return null;
252 }
253
254 /**
255 * Return the next option on the command line -- that is an argument that
256 * starts with '-'. If the next argument is not an option, null is returned.
257 */
258 public String getNextOption() {
259 if (mCurArgData != null) {
260 String prev = mArgs[mArgPos - 1];
261 throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
262 }
263 if (mArgPos >= mArgs.length) {
264 return null;
265 }
266 String arg = mArgs[mArgPos];
267 if (!arg.startsWith("-")) {
268 return null;
269 }
270 mArgPos++;
271 if (arg.equals("--")) {
272 return null;
273 }
274 if (arg.length() > 1 && arg.charAt(1) != '-') {
275 if (arg.length() > 2) {
276 mCurArgData = arg.substring(2);
277 return arg.substring(0, 2);
278 } else {
279 mCurArgData = null;
280 return arg;
281 }
282 }
283 mCurArgData = null;
284 return arg;
285 }
286
287 /**
288 * Return the next argument on the command line, whatever it is; if there are
289 * no arguments left, return null.
290 */
291 public String getNextArg() {
292 if (mCurArgData != null) {
293 String arg = mCurArgData;
294 mCurArgData = null;
295 return arg;
296 } else if (mArgPos < mArgs.length) {
297 return mArgs[mArgPos++];
298 } else {
299 return null;
300 }
301 }
302
303 public String peekNextArg() {
304 if (mCurArgData != null) {
305 return mCurArgData;
306 } else if (mArgPos < mArgs.length) {
307 return mArgs[mArgPos];
308 } else {
309 return null;
310 }
311 }
312
313 /**
314 * Return the next argument on the command line, whatever it is; if there are
315 * no arguments left, throws an IllegalArgumentException to report this to the user.
316 */
317 public String getNextArgRequired() {
318 String arg = getNextArg();
319 if (arg == null) {
320 String prev = mArgs[mArgPos - 1];
321 throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
322 }
323 return arg;
324 }
325
326 /**
327 * Return the {@link ShellCallback} for communicating back with the calling shell.
328 */
329 public ShellCallback getShellCallback() {
330 return mShellCallback;
331 }
332
333 public int handleDefaultCommands(String cmd) {
334 if ("dump".equals(cmd)) {
335 String[] newArgs = new String[mArgs.length-1];
336 System.arraycopy(mArgs, 1, newArgs, 0, mArgs.length-1);
337 mTarget.doDump(mOut, getOutPrintWriter(), newArgs);
338 return 0;
339 } else if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
340 onHelp();
341 } else {
342 getOutPrintWriter().println("Unknown command: " + cmd);
343 }
344 return -1;
345 }
346
347 /**
348 * Implement parsing and execution of a command. If it isn't a command you understand,
349 * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
350 * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
351 * to process additional command line arguments. Command output can be written to
352 * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
353 *
354 * <p class="caution">Note that no permission checking has been done before entering this function,
355 * so you need to be sure to do your own security verification for any commands you
356 * are executing. The easiest way to do this is to have the ShellCommand contain
357 * only a reference to your service's aidl interface, and do all of your command
358 * implementations on top of that -- that way you can rely entirely on your executing security
359 * code behind that interface.</p>
360 *
361 * @param cmd The first command line argument representing the name of the command to execute.
362 * @return Return the command result; generally 0 or positive indicates success and
363 * negative values indicate error.
364 */
365 public abstract int onCommand(String cmd);
366
367 /**
368 * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
369 */
370 public abstract void onHelp();
371}