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