/*
 * Copyright (C) 2020 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.internal.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LocalSocket;
import android.os.SystemProperties;

import java.io.FileDescriptor;
import java.lang.ref.Reference;  // For reachabilityFence.

/**
 * A native-accessible buffer for Zygote commands. Designed to support repeated forking
 * of applications without intervening memory allocation, thus keeping zygote memory
 * as stable as possible.
 * A ZygoteCommandBuffer may have an associated socket from which it can be refilled.
 * Otherwise the contents are explicitly set by getInstance().
 *
 * NOT THREAD-SAFE. No methods may be called concurrently from multiple threads.
 *
 * Only one ZygoteCommandBuffer can exist at a time.
 * Must be explicitly closed before being dropped.
 * @hide
 */
class ZygoteCommandBuffer implements AutoCloseable {
    private long mNativeBuffer;  // Not final so that we can clear it in close().

    /**
     * The command socket.
     *
     * mSocket is retained in the child process in "peer wait" mode, so
     * that it closes when the child process terminates. In other cases,
     * it is closed in the peer.
     */
    private final LocalSocket mSocket;
    private final int mNativeSocket;

    /**
     * Constructs instance from file descriptor from which the command will be read.
     * Only a single instance may be live in a given process. The native code checks.
     *
     * @param fd file descriptor to read from. The setCommand() method may be used if and only if
     * fd is null.
     */
    ZygoteCommandBuffer(@Nullable LocalSocket socket) {
        mSocket = socket;
        if (socket == null) {
            mNativeSocket = -1;
        } else {
            mNativeSocket = mSocket.getFileDescriptor().getInt$();
        }
        mNativeBuffer = getNativeBuffer(mNativeSocket);
    }

    /**
     * Constructs an instance with explicitly supplied arguments and an invalid
     * file descriptor. Can only be used for a single command.
     */
    ZygoteCommandBuffer(@NonNull String[] args) {
        this((LocalSocket) null);
        setCommand(args);
    }


    private static native long getNativeBuffer(int fd);

    /**
     * Deallocate native resources associated with the one and only command buffer, and prevent
     * reuse. Subsequent calls to getInstance() will yield a new buffer.
     * We do not close the associated socket, if any.
     */
    @Override
    public void close() {
        freeNativeBuffer(mNativeBuffer);
        mNativeBuffer = 0;
    }

    private static native void freeNativeBuffer(long /* NativeCommandBuffer* */ nbuffer);

    /**
     * Read at least the first line of the next command into the buffer, return the argument count
     * from that line. Assumes we are initially positioned at the beginning of the first line of
     * the command. Leave the buffer positioned at the beginning of the second command line, i.e.
     * the first argument. If the buffer has no associated file descriptor, we just reposition to
     * the beginning of the buffer, and reread existing contents.  Returns zero if we started out
     * at EOF.
     */
    int getCount() {
        try {
            return nativeGetCount(mNativeBuffer);
        } finally {
            // Make sure the mNativeSocket doesn't get closed due to early finalization.
            Reference.reachabilityFence(mSocket);
        }
    }

    private static native int nativeGetCount(long /* NativeCommandBuffer* */ nbuffer);


    /*
     * Set the buffer to contain the supplied sequence of arguments.
     */
    private void setCommand(String[] command) {
        int nArgs = command.length;
        insert(mNativeBuffer, Integer.toString(nArgs));
        for (String s: command) {
            insert(mNativeBuffer, s);
        }
        // Native code checks there is no socket; hence no reachabilityFence.
    }

    private static native void insert(long /* NativeCommandBuffer* */ nbuffer, String s);

    /**
     * Retrieve the next argument/line from the buffer, filling the buffer as necessary.
     */
    String nextArg() {
        try {
            return nativeNextArg(mNativeBuffer);
        } finally {
            Reference.reachabilityFence(mSocket);
        }
    }

    private static native String nativeNextArg(long /* NativeCommandBuffer* */ nbuffer);

    void readFullyAndReset() {
        try {
            nativeReadFullyAndReset(mNativeBuffer);
        } finally {
            Reference.reachabilityFence(mSocket);
        }
    }

    private static native void nativeReadFullyAndReset(long /* NativeCommandBuffer* */ nbuffer);

    /**
     * Fork a child as specified by the current command in the buffer, and repeat this process
     * after refilling the buffer, so long as the buffer clearly contains another fork command.
     *
     * @param zygoteSocket socket from which to obtain new connections when current one is
     *         disconnected
     * @param expectedUid Peer UID for current connection. We refuse to deal with requests from
     *         a different UID.
     * @param minUid the smallest uid that may be request for the child process.
     * @param firstNiceName The name for the initial process to be forked. Used only for error
     *         reporting.
     *
     * @return true in the child, false in the parent. In the parent case, the buffer is positioned
     * at the beginning of a command that still needs to be processed.
     */
    boolean forkRepeatedly(FileDescriptor zygoteSocket, int expectedUid, int minUid,
                       String firstNiceName, boolean isTopApp) {
        try {
            boolean useFifoUi = SystemProperties.getInt("sys.use_fifo_ui", 0) == 1;
            return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(),
                    expectedUid, minUid, firstNiceName, isTopApp,
                    com.android.internal.os.Flags.zygoteEarlyFifoBoost() ? useFifoUi : false);
        } finally {
            Reference.reachabilityFence(mSocket);
            Reference.reachabilityFence(zygoteSocket);
        }
    }

    /*
     * Repeatedly fork children as above. It commonly does not return in the parent, but it may.
     * @return true in the child, false in the parent if we encounter a command we couldn't handle.
     */
    private static native boolean nativeForkRepeatedly(long /* NativeCommandBuffer* */ nbuffer,
                                                   int zygoteSocketRawFd,
                                                   int expectedUid,
                                                   int minUid,
                                                   String firstNiceName,
                                                   boolean isTopApp,
                                                   boolean useFifoUi);

}
