blob: 5bd5d61b636141ac96de4f84c7ca89ced4f10c95 [file] [log] [blame]
Makoto Onuki4c7073b2019-11-04 10:37:15 -08001/*
2 * Copyright (C) 2019 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.Log;
20
21import java.io.BufferedInputStream;
22import java.io.FileDescriptor;
23import java.io.FileInputStream;
24import java.io.FileOutputStream;
25import java.io.InputStream;
26import java.io.OutputStream;
27import java.io.PrintWriter;
28
29/**
30 * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. This is meant to
31 * be copied into mainline modules, so this class must not use any hidden APIs.
32 *
33 * @hide
34 */
35public abstract class BasicShellCommandHandler {
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
45 private String mCmd;
46 private int mArgPos;
47 private String mCurArgData;
48
49 private FileInputStream mFileIn;
50 private FileOutputStream mFileOut;
51 private FileOutputStream mFileErr;
52
53 private PrintWriter mOutPrintWriter;
54 private PrintWriter mErrPrintWriter;
55 private InputStream mInputStream;
56
57 public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
58 String[] args, int firstArgPos) {
59 mTarget = target;
60 mIn = in;
61 mOut = out;
62 mErr = err;
63 mArgs = args;
64 mCmd = null;
65 mArgPos = firstArgPos;
66 mCurArgData = null;
67 mFileIn = null;
68 mFileOut = null;
69 mFileErr = null;
70 mOutPrintWriter = null;
71 mErrPrintWriter = null;
72 mInputStream = null;
73 }
74
75 public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
76 String[] args) {
77 String cmd;
78 int start;
79 if (args != null && args.length > 0) {
80 cmd = args[0];
81 start = 1;
82 } else {
83 cmd = null;
84 start = 0;
85 }
86 init(target, in, out, err, args, start);
87 mCmd = cmd;
88
89 if (DEBUG) {
90 RuntimeException here = new RuntimeException("here");
91 here.fillInStackTrace();
92 Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
93 Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
94 + " pid=" + Binder.getCallingPid());
95 }
96 int res = -1;
97 try {
98 res = onCommand(mCmd);
99 if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
100 } catch (Throwable e) {
101 // Unlike usual calls, in this case if an exception gets thrown
102 // back to us we want to print it back in to the dump data, since
103 // that is where the caller expects all interesting information to
104 // go.
105 PrintWriter eout = getErrPrintWriter();
106 eout.println();
107 eout.println("Exception occurred while executing: " + e.getMessage());
108 e.printStackTrace(eout);
109 } finally {
110 if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget);
111 if (mOutPrintWriter != null) {
112 mOutPrintWriter.flush();
113 }
114 if (mErrPrintWriter != null) {
115 mErrPrintWriter.flush();
116 }
117 if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget);
118 }
119 if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget);
120 return res;
121 }
122
123 /**
124 * Return the raw FileDescriptor for the output stream.
125 */
126 public FileDescriptor getOutFileDescriptor() {
127 return mOut;
128 }
129
130 /**
131 * Return direct raw access (not buffered) to the command's output data stream.
132 */
133 public OutputStream getRawOutputStream() {
134 if (mFileOut == null) {
135 mFileOut = new FileOutputStream(mOut);
136 }
137 return mFileOut;
138 }
139
140 /**
141 * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
142 */
143 public PrintWriter getOutPrintWriter() {
144 if (mOutPrintWriter == null) {
145 mOutPrintWriter = new PrintWriter(getRawOutputStream());
146 }
147 return mOutPrintWriter;
148 }
149
150 /**
151 * Return the raw FileDescriptor for the error stream.
152 */
153 public FileDescriptor getErrFileDescriptor() {
154 return mErr;
155 }
156
157 /**
158 * Return direct raw access (not buffered) to the command's error output data stream.
159 */
160 public OutputStream getRawErrorStream() {
161 if (mFileErr == null) {
162 mFileErr = new FileOutputStream(mErr);
163 }
164 return mFileErr;
165 }
166
167 /**
168 * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
169 */
170 public PrintWriter getErrPrintWriter() {
171 if (mErr == null) {
172 return getOutPrintWriter();
173 }
174 if (mErrPrintWriter == null) {
175 mErrPrintWriter = new PrintWriter(getRawErrorStream());
176 }
177 return mErrPrintWriter;
178 }
179
180 /**
181 * Return the raw FileDescriptor for the input stream.
182 */
183 public FileDescriptor getInFileDescriptor() {
184 return mIn;
185 }
186
187 /**
188 * Return direct raw access (not buffered) to the command's input data stream.
189 */
190 public InputStream getRawInputStream() {
191 if (mFileIn == null) {
192 mFileIn = new FileInputStream(mIn);
193 }
194 return mFileIn;
195 }
196
197 /**
198 * Return buffered access to the command's {@link #getRawInputStream()}.
199 */
200 public InputStream getBufferedInputStream() {
201 if (mInputStream == null) {
202 mInputStream = new BufferedInputStream(getRawInputStream());
203 }
204 return mInputStream;
205 }
206
207 /**
208 * Return the next option on the command line -- that is an argument that
209 * starts with '-'. If the next argument is not an option, null is returned.
210 */
211 public String getNextOption() {
212 if (mCurArgData != null) {
213 String prev = mArgs[mArgPos - 1];
214 throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
215 }
216 if (mArgPos >= mArgs.length) {
217 return null;
218 }
219 String arg = mArgs[mArgPos];
220 if (!arg.startsWith("-")) {
221 return null;
222 }
223 mArgPos++;
224 if (arg.equals("--")) {
225 return null;
226 }
227 if (arg.length() > 1 && arg.charAt(1) != '-') {
228 if (arg.length() > 2) {
229 mCurArgData = arg.substring(2);
230 return arg.substring(0, 2);
231 } else {
232 mCurArgData = null;
233 return arg;
234 }
235 }
236 mCurArgData = null;
237 return arg;
238 }
239
240 /**
241 * Return the next argument on the command line, whatever it is; if there are
242 * no arguments left, return null.
243 */
244 public String getNextArg() {
245 if (mCurArgData != null) {
246 String arg = mCurArgData;
247 mCurArgData = null;
248 return arg;
249 } else if (mArgPos < mArgs.length) {
250 return mArgs[mArgPos++];
251 } else {
252 return null;
253 }
254 }
255
256 public String peekNextArg() {
257 if (mCurArgData != null) {
258 return mCurArgData;
259 } else if (mArgPos < mArgs.length) {
260 return mArgs[mArgPos];
261 } else {
262 return null;
263 }
264 }
265
266 /**
267 * Return the next argument on the command line, whatever it is; if there are
268 * no arguments left, throws an IllegalArgumentException to report this to the user.
269 */
270 public String getNextArgRequired() {
271 String arg = getNextArg();
272 if (arg == null) {
273 String prev = mArgs[mArgPos - 1];
274 throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
275 }
276 return arg;
277 }
278
279 public int handleDefaultCommands(String cmd) {
280 if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
281 onHelp();
282 } else {
283 getOutPrintWriter().println("Unknown command: " + cmd);
284 }
285 return -1;
286 }
287
288 public Binder getTarget() {
289 return mTarget;
290 }
291
292 public String[] getAllArgs() {
293 return mArgs;
294 }
295
296 /**
297 * Implement parsing and execution of a command. If it isn't a command you understand,
298 * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
299 * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
300 * to process additional command line arguments. Command output can be written to
301 * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
302 *
303 * <p class="caution">Note that no permission checking has been done before entering this
304 * function, so you need to be sure to do your own security verification for any commands you
305 * are executing. The easiest way to do this is to have the ShellCommand contain
306 * only a reference to your service's aidl interface, and do all of your command
307 * implementations on top of that -- that way you can rely entirely on your executing security
308 * code behind that interface.</p>
309 *
310 * @param cmd The first command line argument representing the name of the command to execute.
311 * @return Return the command result; generally 0 or positive indicates success and
312 * negative values indicate error.
313 */
314 public abstract int onCommand(String cmd);
315
316 /**
317 * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
318 */
319 public abstract void onHelp();
320}