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.compat.annotation.UnsupportedAppUsage; 20 import android.util.Slog; 21 22 import com.android.internal.util.FastPrintWriter; 23 24 import java.io.BufferedInputStream; 25 import java.io.FileDescriptor; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.io.PrintWriter; 31 32 /** 33 * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. 34 * @hide 35 */ 36 public 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; 45 private ShellCallback mShellCallback; 46 private ResultReceiver mResultReceiver; 47 48 private String mCmd; 49 private int mArgPos; 50 private String mCurArgData; 51 52 private FileInputStream mFileIn; 53 private FileOutputStream mFileOut; 54 private FileOutputStream mFileErr; 55 56 private FastPrintWriter mOutPrintWriter; 57 private FastPrintWriter mErrPrintWriter; 58 private InputStream mInputStream; 59 init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, int firstArgPos)60 public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, 61 String[] args, ShellCallback callback, int firstArgPos) { 62 mTarget = target; 63 mIn = in; 64 mOut = out; 65 mErr = err; 66 mArgs = args; 67 mShellCallback = callback; 68 mResultReceiver = null; 69 mCmd = null; 70 mArgPos = firstArgPos; 71 mCurArgData = null; 72 mFileIn = null; 73 mFileOut = null; 74 mFileErr = null; 75 mOutPrintWriter = null; 76 mErrPrintWriter = null; 77 mInputStream = null; 78 } 79 exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)80 public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, 81 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 82 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 } 91 init(target, in, out, err, args, callback, start); 92 mCmd = cmd; 93 mResultReceiver = resultReceiver; 94 95 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 } 102 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(); 118 eout.println("Exception occurred while executing:"); 119 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); 129 if (mResultReceiver != null) { 130 mResultReceiver.send(res, null); 131 } 132 } 133 if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget); 134 return res; 135 } 136 137 /** 138 * 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 */ adoptResultReceiver()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 */ getOutFileDescriptor()151 public FileDescriptor getOutFileDescriptor() { 152 return mOut; 153 } 154 155 /** 156 * Return direct raw access (not buffered) to the command's output data stream. 157 */ getRawOutputStream()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 */ getOutPrintWriter()168 public PrintWriter getOutPrintWriter() { 169 if (mOutPrintWriter == null) { 170 mOutPrintWriter = new FastPrintWriter(getRawOutputStream()); 171 } 172 return mOutPrintWriter; 173 } 174 175 /** 176 * Return the raw FileDescriptor for the error stream. 177 */ getErrFileDescriptor()178 public FileDescriptor getErrFileDescriptor() { 179 return mErr; 180 } 181 182 /** 183 * Return direct raw access (not buffered) to the command's error output data stream. 184 */ getRawErrorStream()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 */ getErrPrintWriter()195 public PrintWriter getErrPrintWriter() { 196 if (mErr == null) { 197 return getOutPrintWriter(); 198 } 199 if (mErrPrintWriter == null) { 200 mErrPrintWriter = new FastPrintWriter(getRawErrorStream()); 201 } 202 return mErrPrintWriter; 203 } 204 205 /** 206 * Return the raw FileDescriptor for the input stream. 207 */ getInFileDescriptor()208 public FileDescriptor getInFileDescriptor() { 209 return mIn; 210 } 211 212 /** 213 * Return direct raw access (not buffered) to the command's input data stream. 214 */ getRawInputStream()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 */ getBufferedInputStream()225 public InputStream getBufferedInputStream() { 226 if (mInputStream == null) { 227 mInputStream = new BufferedInputStream(getRawInputStream()); 228 } 229 return mInputStream; 230 } 231 232 /** 233 * Helper for just system services to ask the shell to open an output file. 234 * @hide 235 */ openFileForSystem(String path, String mode)236 public ParcelFileDescriptor openFileForSystem(String path, String mode) { 237 if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode); 238 try { 239 ParcelFileDescriptor pfd = getShellCallback().openFile(path, 240 "u:r:system_server:s0", mode); 241 if (pfd != null) { 242 if (DEBUG) Slog.d(TAG, "Got file: " + pfd); 243 return pfd; 244 } 245 } catch (RuntimeException e) { 246 if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage()); 247 getErrPrintWriter().println("Failure opening file: " + e.getMessage()); 248 } 249 if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path); 250 getErrPrintWriter().println("Error: Unable to open file: " + path); 251 252 String suggestedPath = "/data/local/tmp/"; 253 if (path == null || !path.startsWith(suggestedPath)) { 254 getErrPrintWriter().println("Consider using a file under " + suggestedPath); 255 } 256 return null; 257 } 258 259 /** 260 * 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 */ getNextOption()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 */ getNextArg()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 308 @UnsupportedAppUsage peekNextArg()309 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 319 /** 320 * Returns number of arguments that haven't been processed yet. 321 */ getRemainingArgsCount()322 public int getRemainingArgsCount() { 323 if (mArgPos >= mArgs.length) { 324 return 0; 325 } 326 return mArgs.length - mArgPos; 327 } 328 329 /** 330 * Return the next argument on the command line, whatever it is; if there are 331 * no arguments left, throws an IllegalArgumentException to report this to the user. 332 */ getNextArgRequired()333 public String getNextArgRequired() { 334 String arg = getNextArg(); 335 if (arg == null) { 336 String prev = mArgs[mArgPos - 1]; 337 throw new IllegalArgumentException("Argument expected after \"" + prev + "\""); 338 } 339 return arg; 340 } 341 342 /** 343 * Return the {@link ShellCallback} for communicating back with the calling shell. 344 */ getShellCallback()345 public ShellCallback getShellCallback() { 346 return mShellCallback; 347 } 348 handleDefaultCommands(String cmd)349 public int handleDefaultCommands(String cmd) { 350 if ("dump".equals(cmd)) { 351 String[] newArgs = new String[mArgs.length-1]; 352 System.arraycopy(mArgs, 1, newArgs, 0, mArgs.length-1); 353 mTarget.doDump(mOut, getOutPrintWriter(), newArgs); 354 return 0; 355 } else if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { 356 onHelp(); 357 } else { 358 getOutPrintWriter().println("Unknown command: " + cmd); 359 } 360 return -1; 361 } 362 363 /** 364 * Implement parsing and execution of a command. If it isn't a command you understand, 365 * call {@link #handleDefaultCommands(String)} and return its result as a last resort. 366 * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()} 367 * to process additional command line arguments. Command output can be written to 368 * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}. 369 * 370 * <p class="caution">Note that no permission checking has been done before entering this function, 371 * so you need to be sure to do your own security verification for any commands you 372 * are executing. The easiest way to do this is to have the ShellCommand contain 373 * only a reference to your service's aidl interface, and do all of your command 374 * implementations on top of that -- that way you can rely entirely on your executing security 375 * code behind that interface.</p> 376 * 377 * @param cmd The first command line argument representing the name of the command to execute. 378 * @return Return the command result; generally 0 or positive indicates success and 379 * negative values indicate error. 380 */ onCommand(String cmd)381 public abstract int onCommand(String cmd); 382 383 /** 384 * Implement this to print help text about your command to {@link #getOutPrintWriter()}. 385 */ onHelp()386 public abstract void onHelp(); 387 } 388