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