1 /* 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 17 package android.os; 18 19 import android.util.Slog; 20 21 import com.android.internal.util.FastPrintWriter; 22 23 import java.io.BufferedInputStream; 24 import java.io.FileDescriptor; 25 import java.io.FileInputStream; 26 import java.io.FileOutputStream; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.io.PrintWriter; 30 31 /** 32 * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. 33 * @hide 34 */ 35 public 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 init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, int firstArgPos)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 exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)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 94 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 } 101 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); 128 if (mResultReceiver != null) { 129 mResultReceiver.send(res, null); 130 } 131 } 132 if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget); 133 return res; 134 } 135 136 /** 137 * 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 */ adoptResultReceiver()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 */ getOutFileDescriptor()150 public FileDescriptor getOutFileDescriptor() { 151 return mOut; 152 } 153 154 /** 155 * Return direct raw access (not buffered) to the command's output data stream. 156 */ getRawOutputStream()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 */ getOutPrintWriter()167 public PrintWriter getOutPrintWriter() { 168 if (mOutPrintWriter == null) { 169 mOutPrintWriter = new FastPrintWriter(getRawOutputStream()); 170 } 171 return mOutPrintWriter; 172 } 173 174 /** 175 * Return the raw FileDescriptor for the error stream. 176 */ getErrFileDescriptor()177 public FileDescriptor getErrFileDescriptor() { 178 return mErr; 179 } 180 181 /** 182 * Return direct raw access (not buffered) to the command's error output data stream. 183 */ getRawErrorStream()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 */ getErrPrintWriter()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 /** 205 * Return the raw FileDescriptor for the input stream. 206 */ getInFileDescriptor()207 public FileDescriptor getInFileDescriptor() { 208 return mIn; 209 } 210 211 /** 212 * Return direct raw access (not buffered) to the command's input data stream. 213 */ getRawInputStream()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 */ getBufferedInputStream()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 */ openFileForSystem(String path, String mode)235 public ParcelFileDescriptor openFileForSystem(String path, String mode) { 236 if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode); 237 try { 238 ParcelFileDescriptor pfd = getShellCallback().openFile(path, 239 "u:r:system_server:s0", mode); 240 if (pfd != null) { 241 if (DEBUG) Slog.d(TAG, "Got file: " + pfd); 242 return pfd; 243 } 244 } catch (RuntimeException e) { 245 if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage()); 246 getErrPrintWriter().println("Failure opening file: " + e.getMessage()); 247 } 248 if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path); 249 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 */ getNextOption()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 */ getNextArg()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 peekNextArg()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 */ getNextArgRequired()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 */ getShellCallback()329 public ShellCallback getShellCallback() { 330 return mShellCallback; 331 } 332 handleDefaultCommands(String cmd)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 */ onCommand(String cmd)365 public abstract int onCommand(String cmd); 366 367 /** 368 * Implement this to print help text about your command to {@link #getOutPrintWriter()}. 369 */ onHelp()370 public abstract void onHelp(); 371 } 372