1 /* 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 17 package android.os; 18 19 import android.util.Log; 20 21 import java.io.BufferedInputStream; 22 import java.io.FileDescriptor; 23 import java.io.FileInputStream; 24 import java.io.FileOutputStream; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 import 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 */ 35 public 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 init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, int firstArgPos)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 exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args)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 '" + mCmd + "':"); 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 */ getOutFileDescriptor()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 */ getRawOutputStream()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 */ getOutPrintWriter()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 */ getErrFileDescriptor()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 */ getRawErrorStream()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 */ getErrPrintWriter()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 */ getInFileDescriptor()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 */ getRawInputStream()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 */ getBufferedInputStream()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 */ getNextOption()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 */ getNextArg()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 peekNextArg()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 * Returns number of arguments that haven't been processed yet. 268 */ getRemainingArgsCount()269 public int getRemainingArgsCount() { 270 if (mArgPos >= mArgs.length) { 271 return 0; 272 } 273 return mArgs.length - mArgPos; 274 } 275 276 /** 277 * Return the next argument on the command line, whatever it is; if there are 278 * no arguments left, throws an IllegalArgumentException to report this to the user. 279 */ getNextArgRequired()280 public String getNextArgRequired() { 281 String arg = getNextArg(); 282 if (arg == null) { 283 String prev = mArgs[mArgPos - 1]; 284 throw new IllegalArgumentException("Argument expected after \"" + prev + "\""); 285 } 286 return arg; 287 } 288 handleDefaultCommands(String cmd)289 public int handleDefaultCommands(String cmd) { 290 if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { 291 onHelp(); 292 } else { 293 getOutPrintWriter().println("Unknown command: " + cmd); 294 } 295 return -1; 296 } 297 getTarget()298 public Binder getTarget() { 299 return mTarget; 300 } 301 getAllArgs()302 public String[] getAllArgs() { 303 return mArgs; 304 } 305 306 /** 307 * Implement parsing and execution of a command. If it isn't a command you understand, 308 * call {@link #handleDefaultCommands(String)} and return its result as a last resort. 309 * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()} 310 * to process additional command line arguments. Command output can be written to 311 * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}. 312 * 313 * <p class="caution">Note that no permission checking has been done before entering this 314 * function, so you need to be sure to do your own security verification for any commands you 315 * are executing. The easiest way to do this is to have the ShellCommand contain 316 * only a reference to your service's aidl interface, and do all of your command 317 * implementations on top of that -- that way you can rely entirely on your executing security 318 * code behind that interface.</p> 319 * 320 * @param cmd The first command line argument representing the name of the command to execute. 321 * @return Return the command result; generally 0 or positive indicates success and 322 * negative values indicate error. 323 */ onCommand(String cmd)324 public abstract int onCommand(String cmd); 325 326 /** 327 * Implement this to print help text about your command to {@link #getOutPrintWriter()}. 328 */ onHelp()329 public abstract void onHelp(); 330 } 331