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