content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.os;
  
  import android.annotation.UnsupportedAppUsage;
  import android.util.Slog;
  
  import com.android.internal.util.FastPrintWriter;
  
  import java.io.BufferedInputStream;
  import java.io.FileDescriptor;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.PrintWriter;
  
  /**
   * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
   * @hide
   */
  public abstract class ShellCommand {
      static final String TAG = "ShellCommand";
      static final boolean DEBUG = false;
  
      private Binder mTarget;
      private FileDescriptor mIn;
      private FileDescriptor mOut;
      private FileDescriptor mErr;
      private String[] mArgs;
      private ShellCallback mShellCallback;
      private ResultReceiver mResultReceiver;
  
      private String mCmd;
      private int mArgPos;
      private String mCurArgData;
  
      private FileInputStream mFileIn;
      private FileOutputStream mFileOut;
      private FileOutputStream mFileErr;
  
      private FastPrintWriter mOutPrintWriter;
      private FastPrintWriter mErrPrintWriter;
      private InputStream mInputStream;
  
      public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
              String[] args, ShellCallback callback, int firstArgPos) {
          mTarget = target;
          mIn = in;
          mOut = out;
          mErr = err;
          mArgs = args;
          mShellCallback = callback;
          mResultReceiver = null;
          mCmd = null;
          mArgPos = firstArgPos;
          mCurArgData = null;
          mFileIn = null;
          mFileOut = null;
          mFileErr = null;
          mOutPrintWriter = null;
          mErrPrintWriter = null;
          mInputStream = null;
      }
  
      public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
              String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
          String cmd;
          int start;
          if (args != null && args.length > 0) {
              cmd = args[0];
              start = 1;
          } else {
              cmd = null;
              start = 0;
          }
          init(target, in, out, err, args, callback, start);
          mCmd = cmd;
          mResultReceiver = resultReceiver;
  
          if (DEBUG) {
              RuntimeException here = new RuntimeException("here");
              here.fillInStackTrace();
              Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
              Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
                      + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
          }
          int res = -1;
          try {
              res = onCommand(mCmd);
              if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
          } catch (SecurityException e) {
              PrintWriter eout = getErrPrintWriter();
              eout.println("Security exception: " + e.getMessage());
              eout.println();
              e.printStackTrace(eout);
          } catch (Throwable e) {
              // Unlike usual calls, in this case if an exception gets thrown
              // back to us we want to print it back in to the dump data, since
              // that is where the caller expects all interesting information to
              // go.
              PrintWriter eout = getErrPrintWriter();
              eout.println();
              eout.println("Exception occurred while executing:");
              e.printStackTrace(eout);
          } finally {
              if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
              if (mOutPrintWriter != null) {
                  mOutPrintWriter.flush();
              }
              if (mErrPrintWriter != null) {
                  mErrPrintWriter.flush();
              }
              if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
              if (mResultReceiver != null) {
                  mResultReceiver.send(res, null);
              }
          }
          if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
          return res;
      }
  
      /**
       * Adopt the ResultReceiver that was given to this shell command from it, taking
       * it over.  Primarily used to dispatch to another shell command.  Once called,
       * this shell command will no longer return its own result when done.
       */
      public ResultReceiver adoptResultReceiver() {
          ResultReceiver rr = mResultReceiver;
          mResultReceiver = null;
          return rr;
      }
  
      /**
       * Return the raw FileDescriptor for the output stream.
       */
      public FileDescriptor getOutFileDescriptor() {
          return mOut;
      }
  
      /**
       * Return direct raw access (not buffered) to the command's output data stream.
       */
      public OutputStream getRawOutputStream() {
          if (mFileOut == null) {
              mFileOut = new FileOutputStream(mOut);
          }
          return mFileOut;
      }
  
      /**
       * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
       */
      public PrintWriter getOutPrintWriter() {
          if (mOutPrintWriter == null) {
              mOutPrintWriter = new FastPrintWriter(getRawOutputStream());
          }
          return mOutPrintWriter;
      }
  
      /**
       * Return the raw FileDescriptor for the error stream.
       */
      public FileDescriptor getErrFileDescriptor() {
          return mErr;
      }
  
      /**
       * Return direct raw access (not buffered) to the command's error output data stream.
       */
      public OutputStream getRawErrorStream() {
          if (mFileErr == null) {
              mFileErr = new FileOutputStream(mErr);
          }
          return mFileErr;
      }
  
      /**
       * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
       */
      public PrintWriter getErrPrintWriter() {
          if (mErr == null) {
              return getOutPrintWriter();
          }
          if (mErrPrintWriter == null) {
              mErrPrintWriter = new FastPrintWriter(getRawErrorStream());
          }
          return mErrPrintWriter;
      }
  
      /**
       * Return the raw FileDescriptor for the input stream.
       */
      public FileDescriptor getInFileDescriptor() {
          return mIn;
      }
  
      /**
       * Return direct raw access (not buffered) to the command's input data stream.
       */
      public InputStream getRawInputStream() {
          if (mFileIn == null) {
              mFileIn = new FileInputStream(mIn);
          }
          return mFileIn;
      }
  
      /**
       * Return buffered access to the command's {@link #getRawInputStream()}.
       */
      public InputStream getBufferedInputStream() {
          if (mInputStream == null) {
              mInputStream = new BufferedInputStream(getRawInputStream());
          }
          return mInputStream;
      }
  
      /**
       * Helper for just system services to ask the shell to open an output file.
       * @hide
       */
      public ParcelFileDescriptor openFileForSystem(String path, String mode) {
          if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
          try {
              ParcelFileDescriptor pfd = getShellCallback().openFile(path,
                      "u:r:system_server:s0", mode);
              if (pfd != null) {
                  if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
                  return pfd;
              }
          } catch (RuntimeException e) {
              if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
              getErrPrintWriter().println("Failure opening file: " + e.getMessage());
          }
          if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
          getErrPrintWriter().println("Error: Unable to open file: " + path);
  
          String suggestedPath = "/data/local/tmp/";
          if (path == null || !path.startsWith(suggestedPath)) {
              getErrPrintWriter().println("Consider using a file under " + suggestedPath);
          }
          return null;
      }
  
      /**
       * Return the next option on the command line -- that is an argument that
       * starts with '-'.  If the next argument is not an option, null is returned.
       */
      public String getNextOption() {
          if (mCurArgData != null) {
              String prev = mArgs[mArgPos - 1];
              throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
          }
          if (mArgPos >= mArgs.length) {
              return null;
          }
          String arg = mArgs[mArgPos];
          if (!arg.startsWith("-")) {
              return null;
          }
          mArgPos++;
          if (arg.equals("--")) {
              return null;
          }
          if (arg.length() > 1 && arg.charAt(1) != '-') {
              if (arg.length() > 2) {
                  mCurArgData = arg.substring(2);
                  return arg.substring(0, 2);
              } else {
                  mCurArgData = null;
                  return arg;
              }
          }
          mCurArgData = null;
          return arg;
      }
  
      /**
       * Return the next argument on the command line, whatever it is; if there are
       * no arguments left, return null.
       */
      public String getNextArg() {
          if (mCurArgData != null) {
              String arg = mCurArgData;
              mCurArgData = null;
              return arg;
          } else if (mArgPos < mArgs.length) {
              return mArgs[mArgPos++];
          } else {
              return null;
          }
      }
  
      @UnsupportedAppUsage
      public String peekNextArg() {
          if (mCurArgData != null) {
              return mCurArgData;
          } else if (mArgPos < mArgs.length) {
              return mArgs[mArgPos];
          } else {
              return null;
          }
      }
  
      /**
       * Return the next argument on the command line, whatever it is; if there are
       * no arguments left, throws an IllegalArgumentException to report this to the user.
       */
      public String getNextArgRequired() {
          String arg = getNextArg();
          if (arg == null) {
              String prev = mArgs[mArgPos - 1];
              throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
          }
          return arg;
      }
  
      /**
       * Return the {@link ShellCallback} for communicating back with the calling shell.
       */
      public ShellCallback getShellCallback() {
          return mShellCallback;
      }
  
      public int handleDefaultCommands(String cmd) {
          if ("dump".equals(cmd)) {
              String[] newArgs = new String[mArgs.length-1];
              System.arraycopy(mArgs, 1, newArgs, 0, mArgs.length-1);
              mTarget.doDump(mOut, getOutPrintWriter(), newArgs);
              return 0;
          } else if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
              onHelp();
          } else {
              getOutPrintWriter().println("Unknown command: " + cmd);
          }
          return -1;
      }
  
      /**
       * Implement parsing and execution of a command.  If it isn't a command you understand,
       * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
       * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
       * to process additional command line arguments.  Command output can be written to
       * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
       *
       * <p class="caution">Note that no permission checking has been done before entering this function,
       * so you need to be sure to do your own security verification for any commands you
       * are executing.  The easiest way to do this is to have the ShellCommand contain
       * only a reference to your service's aidl interface, and do all of your command
       * implementations on top of that -- that way you can rely entirely on your executing security
       * code behind that interface.</p>
       *
       * @param cmd The first command line argument representing the name of the command to execute.
       * @return Return the command result; generally 0 or positive indicates success and
       * negative values indicate error.
       */
      public abstract int onCommand(String cmd);
  
      /**
       * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
       */
      public abstract void onHelp();
  }
  