Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4c7073b0 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Bidner shell command API proposal

Bug: 142751512
Test: "adb shell cmd jobschduler help"
Test: "adb shell cmd activity help"

Change-Id: I9174040cae72172e82d260d0ceb9ca5a69b72940
parent 42a1773a
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -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.
+2 −2
Original line number Diff line number Diff line
@@ -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;
+320 −0
Original line number Diff line number Diff line
/*
 * 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();
}
+54 −3
Original line number Diff line number Diff line
@@ -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;
    }

    /**
+15 −280

File changed.

Preview size limit exceeded, changes collapsed.

Loading