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

Commit b468a8fd authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Move NativeDaemonConnector to varargs."

parents a5d24d42 31c6e481
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -1831,7 +1831,11 @@ class MountService extends IMountService.Stub
                // to let the UI to clear itself
                mHandler.postDelayed(new Runnable() {
                    public void run() {
                        try {
                            mConnector.doCommand(String.format("cryptfs restart"));
                        } catch (NativeDaemonConnectorException e) {
                            Slog.e(TAG, "problem executing in background", e);
                        }
                    }
                }, 1000); // 1 second
            }
+159 −132
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;

import com.google.android.collect.Lists;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -32,49 +34,33 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Generic connector class for interfacing with a native
 * daemon which uses the libsysutils FrameworkListener
 * protocol.
 * Generic connector class for interfacing with a native daemon which uses the
 * {@code libsysutils} FrameworkListener protocol.
 */
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
    private static final boolean LOCAL_LOGD = false;
    private static final boolean LOGD = false;

    private final String TAG;

    private BlockingQueue<String> mResponseQueue;
    private OutputStream          mOutputStream;
    private String                TAG = "NativeDaemonConnector";
    private String mSocket;
    private OutputStream mOutputStream;

    private final BlockingQueue<NativeDaemonEvent> mResponseQueue;

    private INativeDaemonConnectorCallbacks mCallbacks;
    private Handler mCallbackHandler;

    /** Lock held whenever communicating with native daemon. */
    private Object mDaemonLock = new Object();
    private final Object mDaemonLock = new Object();

    private final int BUFFER_SIZE = 4096;

    class ResponseCode {
        public static final int ActionInitiated                = 100;

        public static final int CommandOkay                    = 200;

        // The range of 400 -> 599 is reserved for cmd failures
        public static final int OperationFailed                = 400;
        public static final int CommandSyntaxError             = 500;
        public static final int CommandParameterError          = 501;

        public static final int UnsolicitedInformational       = 600;

        //
        public static final int FailedRangeStart               = 400;
        public static final int FailedRangeEnd                 = 599;
    }

    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
                          String socket, int responseQueueSize, String logTag) {
    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
            int responseQueueSize, String logTag) {
        mCallbacks = callbacks;
        if (logTag != null)
            TAG = logTag;
        mSocket = socket;
        mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
        mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
        TAG = logTag != null ? logTag : "NativeDaemonConnector";
    }

    @Override
@@ -136,26 +122,26 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo

                for (int i = 0; i < count; i++) {
                    if (buffer[i] == 0) {
                        String event = new String(buffer, start, i - start);
                        if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
                        final String rawEvent = new String(buffer, start, i - start);
                        if (LOGD) Slog.d(TAG, "RCV <- " + rawEvent);

                        String[] tokens = event.split(" ", 2);
                        try {
                            int code = Integer.parseInt(tokens[0]);

                            if (code >= ResponseCode.UnsolicitedInformational) {
                                mCallbackHandler.sendMessage(
                                        mCallbackHandler.obtainMessage(code, event));
                            final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
                                    rawEvent);
                            if (event.isClassUnsolicited()) {
                                mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
                                        event.getCode(), event.getRawEvent()));
                            } else {
                                try {
                                    mResponseQueue.put(event);
                                } catch (InterruptedException ex) {
                                    Slog.e(TAG, "Failed to put response onto queue", ex);
                                    Slog.e(TAG, "Failed to put response onto queue: " + ex);
                                }
                            }
                        } catch (NumberFormatException nfe) {
                            Slog.w(TAG, String.format("Bad msg (%s)", event));
                        } catch (IllegalArgumentException e) {
                            Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
                        }

                        start = i + 1;
                    }
                }
@@ -195,133 +181,174 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
        }
    }

    private void sendCommandLocked(String command) throws NativeDaemonConnectorException {
        sendCommandLocked(command, null);
    }

    /**
     * Sends a command to the daemon with a single argument
     * Send command to daemon, escaping arguments as needed.
     *
     * @param command  The command to send to the daemon
     * @param argument The argument to send with the command (or null)
     * @return the final command issued.
     */
    private void sendCommandLocked(String command, String argument)
    private String sendCommandLocked(String cmd, Object... args)
            throws NativeDaemonConnectorException {
        if (command != null && command.indexOf('\0') >= 0) {
            throw new IllegalArgumentException("unexpected command: " + command);
        // TODO: eventually enforce that cmd doesn't contain arguments
        if (cmd.indexOf('\0') >= 0) {
            throw new IllegalArgumentException("unexpected command: " + cmd);
        }
        if (argument != null && argument.indexOf('\0') >= 0) {
            throw new IllegalArgumentException("unexpected argument: " + argument);

        final StringBuilder builder = new StringBuilder(cmd);
        for (Object arg : args) {
            final String argString = String.valueOf(arg);
            if (argString.indexOf('\0') >= 0) {
                throw new IllegalArgumentException("unexpected argument: " + arg);
            }

        if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
        if (mOutputStream == null) {
            Slog.e(TAG, "No connection to daemon", new IllegalStateException());
            throw new NativeDaemonConnectorException("No output stream!");
        } else {
            StringBuilder builder = new StringBuilder(command);
            if (argument != null) {
                builder.append(argument);
            builder.append(' ');
            appendEscaped(builder, argString);
        }

        final String unterminated = builder.toString();
        if (LOGD) Slog.d(TAG, "SND -> " + unterminated);

        builder.append('\0');

        if (mOutputStream == null) {
            throw new NativeDaemonConnectorException("missing output stream");
        } else {
            try {
                mOutputStream.write(builder.toString().getBytes());
            } catch (IOException ex) {
                Slog.e(TAG, "IOException in sendCommand", ex);
            } catch (IOException e) {
                throw new NativeDaemonConnectorException("problem sending command", e);
            }
        }

        return unterminated;
    }

    /**
     * Issue a command to the native daemon and return the responses
     * Issue a command to the native daemon and return the responses.
     */
    public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
    public NativeDaemonEvent[] execute(String cmd, Object... args)
            throws NativeDaemonConnectorException {
        synchronized (mDaemonLock) {
            return doCommandLocked(cmd);
            return executeLocked(cmd, args);
        }
    }

    private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException {
    private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
            throws NativeDaemonConnectorException {
        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();

        mResponseQueue.clear();
        sendCommandLocked(cmd);

        ArrayList<String> response = new ArrayList<String>();
        boolean complete = false;
        int code = -1;
        final String sentCommand = sendCommandLocked(cmd, args);

        while (!complete) {
        NativeDaemonEvent event = null;
        do {
            try {
                // TODO - this should not block forever
                String line = mResponseQueue.take();
                if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
                String[] tokens = line.split(" ");
                try {
                    code = Integer.parseInt(tokens[0]);
                } catch (NumberFormatException nfe) {
                    throw new NativeDaemonConnectorException(
                            String.format("Invalid response from daemon (%s)", line));
                event = mResponseQueue.take();
            } catch (InterruptedException e) {
                Slog.w(TAG, "interrupted waiting for event line");
                continue;
            }
            events.add(event);
        } while (event.isClassContinue());

                if ((code >= 200) && (code < 600)) {
                    complete = true;
        if (event.isClassClientError()) {
            throw new NativeDaemonArgumentException(sentCommand, event);
        }
                response.add(line);
            } catch (InterruptedException ex) {
                Slog.e(TAG, "Failed to process response", ex);
        if (event.isClassServerError()) {
            throw new NativeDaemonFailureException(sentCommand, event);
        }

        return events.toArray(new NativeDaemonEvent[events.size()]);
    }

        if (code >= ResponseCode.FailedRangeStart &&
                code <= ResponseCode.FailedRangeEnd) {
            /*
             * Note: The format of the last response in this case is
             *        "NNN <errmsg>"
    /**
     * Issue a command to the native daemon and return the raw responses.
     *
     * @deprecated callers should move to {@link #execute(String, Object...)}
     *             which returns parsed {@link NativeDaemonEvent}.
     */
            throw new NativeDaemonConnectorException(
                    code, cmd, response.get(response.size()-1).substring(4));
    @Deprecated
    public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
        final ArrayList<String> rawEvents = Lists.newArrayList();
        final NativeDaemonEvent[] events = execute(cmd);
        for (NativeDaemonEvent event : events) {
            rawEvents.add(event.getRawEvent());
        }
        return response;
        return rawEvents;
    }

    /**
     * Issues a list command and returns the cooked list
     * Issues a list command and returns the cooked list of all
     * {@link NativeDaemonEvent#getMessage()} which match requested code.
     */
    public String[] doListCommand(String cmd, int expectedResponseCode)
    public String[] doListCommand(String cmd, int expectedCode)
            throws NativeDaemonConnectorException {
        final ArrayList<String> list = Lists.newArrayList();

        final NativeDaemonEvent[] events = execute(cmd);
        for (int i = 0; i < events.length - 1; i++) {
            final NativeDaemonEvent event = events[i];
            final int code = event.getCode();
            if (code == expectedCode) {
                list.add(event.getMessage());
            } else {
                throw new NativeDaemonConnectorException(
                        "unexpected list response " + code + " instead of " + expectedCode);
            }
        }

        final NativeDaemonEvent finalEvent = events[events.length - 1];
        if (!finalEvent.isClassOk()) {
            throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
        }

        ArrayList<String> rsp = doCommand(cmd);
        String[] rdata = new String[rsp.size()-1];
        int idx = 0;
        return list.toArray(new String[list.size()]);
    }

        for (int i = 0; i < rsp.size(); i++) {
            String line = rsp.get(i);
            try {
                String[] tok = line.split(" ");
                int code = Integer.parseInt(tok[0]);
                if (code == expectedResponseCode) {
                    rdata[idx++] = line.substring(tok[0].length() + 1);
                } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
                    if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
                    int last = rsp.size() -1;
                    if (i != last) {
                        Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
                        for (int j = i; j <= last ; j++) {
                            Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
                        }
                    }
                    return rdata;
    /**
     * Append the given argument to {@link StringBuilder}, escaping as needed,
     * and surrounding with quotes when it contains spaces.
     */
    // @VisibleForTesting
    static void appendEscaped(StringBuilder builder, String arg) {
        final boolean hasSpaces = arg.indexOf(' ') >= 0;
        if (hasSpaces) {
            builder.append('"');
        }

        final int length = arg.length();
        for (int i = 0; i < length; i++) {
            final char c = arg.charAt(i);

            if (c == '"') {
                builder.append("\\\"");
            } else if (c == '\\') {
                builder.append("\\\\");
            } else {
                    throw new NativeDaemonConnectorException(
                            String.format("Expected list response %d, but got %d",
                                    expectedResponseCode, code));
                builder.append(c);
            }
            } catch (NumberFormatException nfe) {
                throw new NativeDaemonConnectorException(
                        String.format("Error reading code '%s'", line));
        }

        if (hasSpaces) {
            builder.append('"');
        }
    }

    private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
        public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
            super(command, event);
        }

        @Override
        public IllegalArgumentException rethrowAsParcelableException() {
            throw new IllegalArgumentException(getMessage(), this);
        }
    }

    private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
        public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
            super(command, event);
        }
        throw new NativeDaemonConnectorException("Got an empty response");
    }

    /** {@inheritDoc} */
+23 −13
Original line number Diff line number Diff line
@@ -16,33 +16,43 @@

package com.android.server;

import android.os.Parcel;

/**
 * An exception that indicates there was an error with a NativeDaemonConnector operation
 * An exception that indicates there was an error with a
 * {@link NativeDaemonConnector} operation.
 */
public class NativeDaemonConnectorException extends RuntimeException
{
    private int mCode = -1;
public class NativeDaemonConnectorException extends Exception {
    private String mCmd;
    private NativeDaemonEvent mEvent;

    public NativeDaemonConnectorException() {}
    public NativeDaemonConnectorException(String detailMessage) {
        super(detailMessage);
    }

    public NativeDaemonConnectorException(String error)
    {
        super(error);
    public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
        super(detailMessage, throwable);
    }

    public NativeDaemonConnectorException(int code, String cmd, String error)
    {
        super(String.format("Cmd {%s} failed with code %d : {%s}", cmd, code, error));
        mCode = code;
    public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
        super("command '" + cmd + "' failed with '" + event + "'");
        mCmd = cmd;
        mEvent = event;
    }

    public int getCode() {
        return mCode;
        return mEvent.getCode();
    }

    public String getCmd() {
        return mCmd;
    }

    /**
     * Rethrow as a {@link RuntimeException} subclass that is handled by
     * {@link Parcel#writeException(Exception)}.
     */
    public IllegalArgumentException rethrowAsParcelableException() {
        throw new IllegalStateException(getMessage(), this);
    }
}
+113 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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 com.android.server;

/**
 * Parsed event from native side of {@link NativeDaemonConnector}.
 */
public class NativeDaemonEvent {

    // TODO: keep class ranges in sync with ResponseCode.h
    // TODO: swap client and server error ranges to roughly mirror HTTP spec

    private final int mCode;
    private final String mMessage;
    private final String mRawEvent;

    private NativeDaemonEvent(int code, String message, String rawEvent) {
        mCode = code;
        mMessage = message;
        mRawEvent = rawEvent;
    }

    public int getCode() {
        return mCode;
    }

    public String getMessage() {
        return mMessage;
    }

    @Deprecated
    public String getRawEvent() {
        return mRawEvent;
    }

    @Override
    public String toString() {
        return mRawEvent;
    }

    /**
     * Test if event represents a partial response which is continued in
     * additional subsequent events.
     */
    public boolean isClassContinue() {
        return mCode >= 100 && mCode < 200;
    }

    /**
     * Test if event represents a command success.
     */
    public boolean isClassOk() {
        return mCode >= 200 && mCode < 300;
    }

    /**
     * Test if event represents a remote native daemon error.
     */
    public boolean isClassServerError() {
        return mCode >= 400 && mCode < 500;
    }

    /**
     * Test if event represents a command syntax or argument error.
     */
    public boolean isClassClientError() {
        return mCode >= 500 && mCode < 600;
    }

    /**
     * Test if event represents an unsolicited event from native daemon.
     */
    public boolean isClassUnsolicited() {
        return mCode >= 600 && mCode < 700;
    }

    /**
     * Parse the given raw event into {@link NativeDaemonEvent} instance.
     *
     * @throws IllegalArgumentException when line doesn't match format expected
     *             from native side.
     */
    public static NativeDaemonEvent parseRawEvent(String rawEvent) {
        final int splitIndex = rawEvent.indexOf(' ');
        if (splitIndex == -1) {
            throw new IllegalArgumentException("unable to find ' ' separator");
        }

        final int code;
        try {
            code = Integer.parseInt(rawEvent.substring(0, splitIndex));
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("problem parsing code", e);
        }

        final String message = rawEvent.substring(splitIndex + 1);
        return new NativeDaemonEvent(code, message, rawEvent);
    }
}
+16 −18
Original line number Diff line number Diff line
@@ -442,27 +442,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
    @Override
    public void setInterfaceDown(String iface) {
        mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
        try {
            InterfaceConfiguration ifcg = getInterfaceConfig(iface);
        final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
        ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
        setInterfaceConfig(iface, ifcg);
        } catch (NativeDaemonConnectorException e) {
            throw new IllegalStateException(
                    "Unable to communicate with native daemon for interface down - " + e);
        }
    }

    @Override
    public void setInterfaceUp(String iface) {
        mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
        try {
            InterfaceConfiguration ifcg = getInterfaceConfig(iface);
        final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
        ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
        setInterfaceConfig(iface, ifcg);
        } catch (NativeDaemonConnectorException e) {
            throw new IllegalStateException(
                    "Unable to communicate with native daemon for interface up - " + e);
        }
    }

    @Override
@@ -733,7 +723,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
    @Override
    public void setIpForwardingEnabled(boolean enable) {
        mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
        try {
            mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis")));
        } catch (NativeDaemonConnectorException e) {
            e.rethrowAsParcelableException();
        }
    }

    @Override
@@ -875,7 +869,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
            }
        }

        try {
            mConnector.doCommand(cmd);
        } catch (NativeDaemonConnectorException e) {
            e.rethrowAsParcelableException();
        }
    }

    @Override
Loading