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