diff options
6 files changed, 399 insertions, 294 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index eb22d0907525..593e49490e94 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -59,9 +59,7 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; -import android.os.ResultReceiver; import android.os.ServiceManager; -import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManagerInternal; @@ -2690,12 +2688,13 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - (new JobSchedulerShellCommand(JobSchedulerService.this)).exec( - this, in, out, err, args, callback, resultReceiver); + protected int handleShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out, + @NonNull FileDescriptor err, @NonNull String[] args) { + return (new JobSchedulerShellCommand(JobSchedulerService.this)).exec( + this, in, out, err, args); } + /** * <b>For internal system user only!</b> * Returns a list of all currently-executing jobs. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index 01d158ba9452..a5c6c0132fc8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -20,13 +20,13 @@ import android.app.ActivityManager; import android.app.AppGlobals; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.os.BasicShellCommandHandler; import android.os.Binder; -import android.os.ShellCommand; import android.os.UserHandle; import java.io.PrintWriter; -public final class JobSchedulerShellCommand extends ShellCommand { +public final class JobSchedulerShellCommand extends BasicShellCommandHandler { public static final int CMD_ERR_NO_PACKAGE = -1000; public static final int CMD_ERR_NO_JOB = -1001; public static final int CMD_ERR_CONSTRAINTS = -1002; diff --git a/core/java/android/os/BasicShellCommandHandler.java b/core/java/android/os/BasicShellCommandHandler.java new file mode 100644 index 000000000000..5bd5d61b6361 --- /dev/null +++ b/core/java/android/os/BasicShellCommandHandler.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2019 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.util.Log; + +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}. This is meant to + * be copied into mainline modules, so this class must not use any hidden APIs. + * + * @hide + */ +public abstract class BasicShellCommandHandler { + 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 String mCmd; + private int mArgPos; + private String mCurArgData; + + private FileInputStream mFileIn; + private FileOutputStream mFileOut; + private FileOutputStream mFileErr; + + private PrintWriter mOutPrintWriter; + private PrintWriter mErrPrintWriter; + private InputStream mInputStream; + + public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, int firstArgPos) { + mTarget = target; + mIn = in; + mOut = out; + mErr = err; + mArgs = args; + 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) { + 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, start); + mCmd = cmd; + + if (DEBUG) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here); + Log.d(TAG, "Calling uid=" + Binder.getCallingUid() + + " pid=" + Binder.getCallingPid()); + } + int res = -1; + try { + res = onCommand(mCmd); + if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget); + } 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.getMessage()); + e.printStackTrace(eout); + } finally { + if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget); + if (mOutPrintWriter != null) { + mOutPrintWriter.flush(); + } + if (mErrPrintWriter != null) { + mErrPrintWriter.flush(); + } + if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget); + } + if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget); + return res; + } + + /** + * 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 PrintWriter(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 PrintWriter(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; + } + + /** + * 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; + } + } + + 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; + } + + public int handleDefaultCommands(String cmd) { + if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { + onHelp(); + } else { + getOutPrintWriter().println("Unknown command: " + cmd); + } + return -1; + } + + public Binder getTarget() { + return mTarget; + } + + public String[] getAllArgs() { + return mArgs; + } + + /** + * 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(); +} diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index ef3afabfe878..a856975e2501 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -37,7 +37,9 @@ import libcore.io.IoUtils; import libcore.util.NativeAllocationRegistry; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Modifier; @@ -905,11 +907,60 @@ public class Binder implements IBinder { @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) throws RemoteException { - FileOutputStream fout = new FileOutputStream(err != null ? err : out); - PrintWriter pw = new FastPrintWriter(fout); + + // First, convert in, out and err to @NonNull, by redirecting any that's null to /dev/null. + try { + if (in == null) { + in = new FileInputStream("/dev/null").getFD(); + } + if (out == null) { + out = new FileOutputStream("/dev/null").getFD(); + } + if (err == null) { + err = out; + } + } catch (IOException e) { + PrintWriter pw = new FastPrintWriter(new FileOutputStream(err != null ? err : out)); + pw.println("Failed to open /dev/null: " + e.getMessage()); + pw.flush(); + resultReceiver.send(-1, null); + return; + } + // Also make args @NonNull. + if (args == null) { + args = new String[0]; + } + + int result = -1; + try { + result = handleShellCommand(in, out, err, args); + } finally { + resultReceiver.send(result, null); + } + } + + /** + * System services can implement this method to implement ADB shell commands. + * + * TODO More Javadoc. + * TODO Add a generic way to define subcommands and their permissions. + * + * @param in standard input. + * @param out standard output. + * @param err standard error. + * @param args arguments passed to the command. Can be empty. The first argument is typically + * a subcommand, such as {@code run} for {@code adb shell cmd jobscheduler run}. + * + * @hide + */ + // @SystemApi TODO Make it a system API. + protected int handleShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out, + @NonNull FileDescriptor err, @NonNull String[] args) { + FileOutputStream ferr = new FileOutputStream(err); + PrintWriter pw = new FastPrintWriter(ferr); pw.println("No shell command implementation."); pw.flush(); - resultReceiver.send(0, null); + return 0; } /** diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java index 2fe8726a0915..f3a6869687dc 100644 --- a/core/java/android/os/ShellCommand.java +++ b/core/java/android/os/ShellCommand.java @@ -33,105 +33,21 @@ 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; +public abstract class ShellCommand extends BasicShellCommandHandler { 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; + mShellCallback = callback; mResultReceiver = resultReceiver; + final int result = super.exec(target, in, out, err, args); - 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()); + if (mResultReceiver != null) { + mResultReceiver.send(result, null); } - 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; + + return result; } /** @@ -146,90 +62,6 @@ public abstract class ShellCommand { } /** - * 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 */ @@ -256,77 +88,19 @@ public abstract class ShellCommand { 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; + public int handleDefaultCommands(String cmd) { + if ("dump".equals(cmd)) { + String[] newArgs = new String[getAllArgs().length-1]; + System.arraycopy(getAllArgs(), 1, newArgs, 0, getAllArgs().length-1); + getTarget().doDump(getOutFileDescriptor(), getOutPrintWriter(), newArgs); + return 0; } + return super.handleDefaultCommands(cmd); } @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 super.peekNextArg(); } /** @@ -335,43 +109,4 @@ public abstract class ShellCommand { 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(); } diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java index 278f40660ee9..e1b8e6c2d7d1 100644 --- a/core/java/com/android/internal/os/BaseCommand.java +++ b/core/java/com/android/internal/os/BaseCommand.java @@ -18,14 +18,14 @@ package com.android.internal.os; import android.annotation.UnsupportedAppUsage; -import android.os.ShellCommand; +import android.os.BasicShellCommandHandler; import java.io.PrintStream; public abstract class BaseCommand { @UnsupportedAppUsage - final protected ShellCommand mArgs = new ShellCommand() { + final protected BasicShellCommandHandler mArgs = new BasicShellCommandHandler() { @Override public int onCommand(String cmd) { return 0; } @@ -50,7 +50,7 @@ public abstract class BaseCommand { } mRawArgs = args; - mArgs.init(null, null, null, null, args, null, 0); + mArgs.init(null, null, null, null, args, 0); try { onRun(); |