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
17package android.os;
18
19import android.util.Slog;
20
21import com.android.internal.util.FastPrintWriter;
22
23import java.io.BufferedInputStream;
24import java.io.FileDescriptor;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.io.PrintWriter;
30
31/**
32 * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
33 * @hide
34 */
35public 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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
329    public ShellCallback getShellCallback() {
330        return mShellCallback;
331    }
332
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     */
365    public abstract int onCommand(String cmd);
366
367    /**
368     * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
369     */
370    public abstract void onHelp();
371}
372