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