blob: 83a68ca3ab7e104483a9ed9d98397e2ebfc1ea61 [file] [log] [blame]
/*
* 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 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) {
try {
return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(),
expectedUid, minUid, firstNiceName);
} 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);
}