blob: 2fe8726a091536e6188874eec1775ffbea75ea56 [file] [log] [blame]
Dianne Hackborn9461b6f2015-10-07 17:33:16 -07001/*
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
Andrei Onea24ec3212019-03-15 17:35:05 +000019import android.annotation.UnsupportedAppUsage;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070020import android.util.Slog;
Dianne Hackbornc81983a2017-10-20 16:16:32 -070021
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070022import com.android.internal.util.FastPrintWriter;
23
Todd Kennedy72cfcd02015-11-03 17:08:55 -080024import java.io.BufferedInputStream;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070025import java.io.FileDescriptor;
Todd Kennedy72cfcd02015-11-03 17:08:55 -080026import java.io.FileInputStream;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070027import java.io.FileOutputStream;
Todd Kennedy72cfcd02015-11-03 17:08:55 -080028import java.io.InputStream;
Dianne Hackborn2e931f52016-01-28 12:21:17 -080029import java.io.OutputStream;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070030import java.io.PrintWriter;
31
32/**
Dianne Hackborn2e931f52016-01-28 12:21:17 -080033 * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070034 * @hide
35 */
36public abstract class ShellCommand {
37 static final String TAG = "ShellCommand";
38 static final boolean DEBUG = false;
39
40 private Binder mTarget;
41 private FileDescriptor mIn;
42 private FileDescriptor mOut;
43 private FileDescriptor mErr;
44 private String[] mArgs;
Dianne Hackborn354736e2016-08-22 17:00:05 -070045 private ShellCallback mShellCallback;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070046 private ResultReceiver mResultReceiver;
47
48 private String mCmd;
49 private int mArgPos;
50 private String mCurArgData;
51
Dianne Hackborn2e931f52016-01-28 12:21:17 -080052 private FileInputStream mFileIn;
53 private FileOutputStream mFileOut;
54 private FileOutputStream mFileErr;
55
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070056 private FastPrintWriter mOutPrintWriter;
57 private FastPrintWriter mErrPrintWriter;
Todd Kennedy72cfcd02015-11-03 17:08:55 -080058 private InputStream mInputStream;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070059
Dianne Hackborn3cdb56e2015-11-11 12:45:44 -080060 public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
Dianne Hackborn354736e2016-08-22 17:00:05 -070061 String[] args, ShellCallback callback, int firstArgPos) {
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070062 mTarget = target;
63 mIn = in;
64 mOut = out;
65 mErr = err;
66 mArgs = args;
Dianne Hackborn354736e2016-08-22 17:00:05 -070067 mShellCallback = callback;
Dianne Hackborn3cdb56e2015-11-11 12:45:44 -080068 mResultReceiver = null;
69 mCmd = null;
70 mArgPos = firstArgPos;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070071 mCurArgData = null;
Dianne Hackborn2e931f52016-01-28 12:21:17 -080072 mFileIn = null;
73 mFileOut = null;
74 mFileErr = null;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070075 mOutPrintWriter = null;
76 mErrPrintWriter = null;
Dianne Hackborn2e931f52016-01-28 12:21:17 -080077 mInputStream = null;
Dianne Hackborn3cdb56e2015-11-11 12:45:44 -080078 }
79
80 public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
Dianne Hackborn354736e2016-08-22 17:00:05 -070081 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
Dianne Hackborn3cdb56e2015-11-11 12:45:44 -080082 String cmd;
83 int start;
84 if (args != null && args.length > 0) {
85 cmd = args[0];
86 start = 1;
87 } else {
88 cmd = null;
89 start = 0;
90 }
Dianne Hackborn354736e2016-08-22 17:00:05 -070091 init(target, in, out, err, args, callback, start);
Dianne Hackborn3cdb56e2015-11-11 12:45:44 -080092 mCmd = cmd;
93 mResultReceiver = resultReceiver;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -070094
Dianne Hackborn1704e3c2017-10-31 19:55:42 +000095 if (DEBUG) {
96 RuntimeException here = new RuntimeException("here");
97 here.fillInStackTrace();
98 Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
99 Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
100 + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
101 }
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700102 int res = -1;
103 try {
104 res = onCommand(mCmd);
105 if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
106 } catch (SecurityException e) {
107 PrintWriter eout = getErrPrintWriter();
108 eout.println("Security exception: " + e.getMessage());
109 eout.println();
110 e.printStackTrace(eout);
111 } catch (Throwable e) {
112 // Unlike usual calls, in this case if an exception gets thrown
113 // back to us we want to print it back in to the dump data, since
114 // that is where the caller expects all interesting information to
115 // go.
116 PrintWriter eout = getErrPrintWriter();
117 eout.println();
Dianne Hackborn354736e2016-08-22 17:00:05 -0700118 eout.println("Exception occurred while executing:");
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700119 e.printStackTrace(eout);
120 } finally {
121 if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
122 if (mOutPrintWriter != null) {
123 mOutPrintWriter.flush();
124 }
125 if (mErrPrintWriter != null) {
126 mErrPrintWriter.flush();
127 }
128 if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700129 if (mResultReceiver != null) {
130 mResultReceiver.send(res, null);
131 }
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700132 }
133 if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
Dianne Hackborn2e441072015-10-28 18:00:57 -0700134 return res;
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700135 }
136
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800137 /**
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700138 * Adopt the ResultReceiver that was given to this shell command from it, taking
139 * it over. Primarily used to dispatch to another shell command. Once called,
140 * this shell command will no longer return its own result when done.
141 */
142 public ResultReceiver adoptResultReceiver() {
143 ResultReceiver rr = mResultReceiver;
144 mResultReceiver = null;
145 return rr;
146 }
147
148 /**
149 * Return the raw FileDescriptor for the output stream.
150 */
151 public FileDescriptor getOutFileDescriptor() {
152 return mOut;
153 }
154
155 /**
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800156 * Return direct raw access (not buffered) to the command's output data stream.
157 */
158 public OutputStream getRawOutputStream() {
159 if (mFileOut == null) {
160 mFileOut = new FileOutputStream(mOut);
161 }
162 return mFileOut;
163 }
164
165 /**
166 * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
167 */
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700168 public PrintWriter getOutPrintWriter() {
169 if (mOutPrintWriter == null) {
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800170 mOutPrintWriter = new FastPrintWriter(getRawOutputStream());
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700171 }
172 return mOutPrintWriter;
173 }
174
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800175 /**
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700176 * Return the raw FileDescriptor for the error stream.
177 */
178 public FileDescriptor getErrFileDescriptor() {
179 return mErr;
180 }
181
182 /**
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800183 * Return direct raw access (not buffered) to the command's error output data stream.
184 */
185 public OutputStream getRawErrorStream() {
186 if (mFileErr == null) {
187 mFileErr = new FileOutputStream(mErr);
188 }
189 return mFileErr;
190 }
191
192 /**
193 * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
194 */
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700195 public PrintWriter getErrPrintWriter() {
196 if (mErr == null) {
197 return getOutPrintWriter();
198 }
199 if (mErrPrintWriter == null) {
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800200 mErrPrintWriter = new FastPrintWriter(getRawErrorStream());
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700201 }
202 return mErrPrintWriter;
203 }
204
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800205 /**
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700206 * Return the raw FileDescriptor for the input stream.
207 */
208 public FileDescriptor getInFileDescriptor() {
209 return mIn;
210 }
211
212 /**
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800213 * Return direct raw access (not buffered) to the command's input data stream.
214 */
215 public InputStream getRawInputStream() {
216 if (mFileIn == null) {
217 mFileIn = new FileInputStream(mIn);
218 }
219 return mFileIn;
220 }
221
222 /**
223 * Return buffered access to the command's {@link #getRawInputStream()}.
224 */
225 public InputStream getBufferedInputStream() {
Todd Kennedy72cfcd02015-11-03 17:08:55 -0800226 if (mInputStream == null) {
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800227 mInputStream = new BufferedInputStream(getRawInputStream());
Todd Kennedy72cfcd02015-11-03 17:08:55 -0800228 }
229 return mInputStream;
230 }
231
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700232 /**
Dianne Hackborn331084d2016-10-07 17:57:00 -0700233 * Helper for just system services to ask the shell to open an output file.
234 * @hide
235 */
Dianne Hackbornca3872c2017-10-30 14:19:32 -0700236 public ParcelFileDescriptor openFileForSystem(String path, String mode) {
Dianne Hackborn1704e3c2017-10-31 19:55:42 +0000237 if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
Dianne Hackborn331084d2016-10-07 17:57:00 -0700238 try {
Dianne Hackbornca3872c2017-10-30 14:19:32 -0700239 ParcelFileDescriptor pfd = getShellCallback().openFile(path,
240 "u:r:system_server:s0", mode);
Dianne Hackborn331084d2016-10-07 17:57:00 -0700241 if (pfd != null) {
Dianne Hackborn1704e3c2017-10-31 19:55:42 +0000242 if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
Dianne Hackborn331084d2016-10-07 17:57:00 -0700243 return pfd;
244 }
245 } catch (RuntimeException e) {
Dianne Hackborn1704e3c2017-10-31 19:55:42 +0000246 if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
Dianne Hackborn331084d2016-10-07 17:57:00 -0700247 getErrPrintWriter().println("Failure opening file: " + e.getMessage());
248 }
Dianne Hackborn1704e3c2017-10-31 19:55:42 +0000249 if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
Dianne Hackborn331084d2016-10-07 17:57:00 -0700250 getErrPrintWriter().println("Error: Unable to open file: " + path);
Robin Lee56be27c2017-09-14 14:28:26 +0200251
252 String suggestedPath = "/data/local/tmp/";
253 if (path == null || !path.startsWith(suggestedPath)) {
254 getErrPrintWriter().println("Consider using a file under " + suggestedPath);
255 }
Dianne Hackborn331084d2016-10-07 17:57:00 -0700256 return null;
257 }
258
259 /**
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700260 * Return the next option on the command line -- that is an argument that
261 * starts with '-'. If the next argument is not an option, null is returned.
262 */
263 public String getNextOption() {
264 if (mCurArgData != null) {
265 String prev = mArgs[mArgPos - 1];
266 throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
267 }
268 if (mArgPos >= mArgs.length) {
269 return null;
270 }
271 String arg = mArgs[mArgPos];
272 if (!arg.startsWith("-")) {
273 return null;
274 }
275 mArgPos++;
276 if (arg.equals("--")) {
277 return null;
278 }
279 if (arg.length() > 1 && arg.charAt(1) != '-') {
280 if (arg.length() > 2) {
281 mCurArgData = arg.substring(2);
282 return arg.substring(0, 2);
283 } else {
284 mCurArgData = null;
285 return arg;
286 }
287 }
288 mCurArgData = null;
289 return arg;
290 }
291
292 /**
293 * Return the next argument on the command line, whatever it is; if there are
294 * no arguments left, return null.
295 */
296 public String getNextArg() {
297 if (mCurArgData != null) {
298 String arg = mCurArgData;
299 mCurArgData = null;
300 return arg;
301 } else if (mArgPos < mArgs.length) {
302 return mArgs[mArgPos++];
303 } else {
304 return null;
305 }
306 }
307
Andrei Onea24ec3212019-03-15 17:35:05 +0000308 @UnsupportedAppUsage
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800309 public String peekNextArg() {
310 if (mCurArgData != null) {
311 return mCurArgData;
312 } else if (mArgPos < mArgs.length) {
313 return mArgs[mArgPos];
314 } else {
315 return null;
316 }
317 }
318
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700319 /**
320 * Return the next argument on the command line, whatever it is; if there are
321 * no arguments left, throws an IllegalArgumentException to report this to the user.
322 */
323 public String getNextArgRequired() {
324 String arg = getNextArg();
325 if (arg == null) {
326 String prev = mArgs[mArgPos - 1];
327 throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
328 }
329 return arg;
330 }
331
Dianne Hackborn354736e2016-08-22 17:00:05 -0700332 /**
333 * Return the {@link ShellCallback} for communicating back with the calling shell.
334 */
335 public ShellCallback getShellCallback() {
336 return mShellCallback;
337 }
338
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700339 public int handleDefaultCommands(String cmd) {
340 if ("dump".equals(cmd)) {
341 String[] newArgs = new String[mArgs.length-1];
342 System.arraycopy(mArgs, 1, newArgs, 0, mArgs.length-1);
343 mTarget.doDump(mOut, getOutPrintWriter(), newArgs);
344 return 0;
345 } else if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
346 onHelp();
347 } else {
348 getOutPrintWriter().println("Unknown command: " + cmd);
349 }
350 return -1;
351 }
352
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800353 /**
354 * Implement parsing and execution of a command. If it isn't a command you understand,
355 * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
Leonard Mosescuf3409ce2016-10-06 17:32:05 -0700356 * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800357 * to process additional command line arguments. Command output can be written to
358 * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
359 *
360 * <p class="caution">Note that no permission checking has been done before entering this function,
361 * so you need to be sure to do your own security verification for any commands you
362 * are executing. The easiest way to do this is to have the ShellCommand contain
363 * only a reference to your service's aidl interface, and do all of your command
364 * implementations on top of that -- that way you can rely entirely on your executing security
365 * code behind that interface.</p>
366 *
367 * @param cmd The first command line argument representing the name of the command to execute.
368 * @return Return the command result; generally 0 or positive indicates success and
369 * negative values indicate error.
370 */
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700371 public abstract int onCommand(String cmd);
372
Dianne Hackborn2e931f52016-01-28 12:21:17 -0800373 /**
374 * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
375 */
Dianne Hackborn9461b6f2015-10-07 17:33:16 -0700376 public abstract void onHelp();
377}