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