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