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