Home | History | Annotate | Download | only in os
      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.util.Slog;
     20 
     21 import com.android.internal.util.FastPrintWriter;
     22 
     23 import java.io.BufferedInputStream;
     24 import java.io.FileDescriptor;
     25 import java.io.FileInputStream;
     26 import java.io.FileOutputStream;
     27 import java.io.InputStream;
     28 import java.io.OutputStream;
     29 import java.io.PrintWriter;
     30 
     31 /**
     32  * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
     33  * @hide
     34  */
     35 public 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