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 
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         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 
exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ResultReceiver resultReceiver)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      */
getRawOutputStream()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      */
getOutPrintWriter()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      */
getRawErrorStream()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      */
getErrPrintWriter()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      */
getRawInputStream()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      */
getBufferedInputStream()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      */
getNextOption()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      */
getNextArg()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 
peekNextArg()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      */
getNextArgRequired()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 
handleDefaultCommands(String cmd)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      */
onCommand(String cmd)292     public abstract int onCommand(String cmd);
293 
294     /**
295      * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
296      */
onHelp()297     public abstract void onHelp();
298 }
299