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