blob: 4cb2c5979525884a672a6a67d057879022897883 [file] [log] [blame]
/*
* Copyright (C) 2010 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 dalvik.system;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketOptions;
import org.apache.harmony.luni.platform.IFileSystem;
import org.apache.harmony.luni.platform.INetworkSystem;
/**
* Mechanism to let threads set restrictions on what code is allowed
* to do in their thread.
*
* <p>This is meant for applications to prevent certain blocking
* operations from running on their main event loop (or "UI") threads.
*
* <p>Note that this is all best-effort to catch most accidental mistakes
* and isn't intended to be a perfect mechanism, nor provide any sort of
* security.
*
* @hide
*/
public final class BlockGuard {
public static final int DISALLOW_DISK_WRITE = 0x01;
public static final int DISALLOW_DISK_READ = 0x02;
public static final int DISALLOW_NETWORK = 0x04;
public static final int PASS_RESTRICTIONS_VIA_RPC = 0x08;
public static final int PENALTY_LOG = 0x10;
public static final int PENALTY_DIALOG = 0x20;
public static final int PENALTY_DEATH = 0x40;
public interface Policy {
/**
* Called on disk writes.
*/
void onWriteToDisk();
/**
* Called on disk writes.
*/
void onReadFromDisk();
/**
* Called on network operations.
*/
void onNetwork();
/**
* Returns the policy bitmask, for shipping over Binder calls
* to remote threads/processes and reinstantiating the policy
* there. The bits in the mask are from the DISALLOW_* and
* PENALTY_* constants.
*/
int getPolicyMask();
}
public static class BlockGuardPolicyException extends RuntimeException {
// bitmask of DISALLOW_*, PENALTY_*, etc flags
private final int mPolicyState;
private final int mPolicyViolated;
public BlockGuardPolicyException(int policyState, int policyViolated) {
mPolicyState = policyState;
mPolicyViolated = policyViolated;
fillInStackTrace();
}
public int getPolicy() {
return mPolicyState;
}
public int getPolicyViolation() {
return mPolicyViolated;
}
public String getMessage() {
// Note: do not change this format casually. It's
// somewhat unfortunately Parceled and passed around
// Binder calls and parsed back into an Exception by
// Android's StrictMode. This was the least invasive
// option and avoided a gross mix of Java Serialization
// combined with Parcels.
return "policy=" + mPolicyState + " violation=" + mPolicyViolated;
}
}
/**
* The default, permissive policy that doesn't prevent any operations.
*/
public static Policy LAX_POLICY = new Policy() {
public void onWriteToDisk() {}
public void onReadFromDisk() {}
public void onNetwork() {}
public int getPolicyMask() {
return 0;
}
};
private static ThreadLocal<Policy> threadPolicy = new ThreadLocal<Policy>() {
@Override protected Policy initialValue() {
return LAX_POLICY;
}
};
/**
* Get the current thread's policy.
*
* @return the current thread's policy. Never returns null.
* Will return the LAX_POLICY instance if nothing else is set.
*/
public static Policy getThreadPolicy() {
return threadPolicy.get();
}
/**
* Sets the current thread's block guard policy.
*
* @param policy policy to set. May not be null. Use the public LAX_POLICY
* if you want to unset the active policy.
*/
public static void setThreadPolicy(Policy policy) {
if (policy == null) {
throw new NullPointerException("policy == null");
}
threadPolicy.set(policy);
}
private BlockGuard() {}
/**
* A filesystem wrapper that calls the policy check functions
* on reads and writes.
*/
public static class WrappedFileSystem implements IFileSystem {
private final IFileSystem mFileSystem;
public WrappedFileSystem(IFileSystem fileSystem) {
mFileSystem = fileSystem;
}
public long read(int fileDescriptor, byte[] bytes, int offset, int length)
throws IOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return mFileSystem.read(fileDescriptor, bytes, offset, length);
}
public long write(int fileDescriptor, byte[] bytes, int offset, int length)
throws IOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return mFileSystem.write(fileDescriptor, bytes, offset, length);
}
public long readv(int fileDescriptor, int[] addresses, int[] offsets,
int[] lengths, int size) throws IOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return mFileSystem.readv(fileDescriptor, addresses, offsets, lengths, size);
}
public long writev(int fileDescriptor, int[] addresses, int[] offsets,
int[] lengths, int size) throws IOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return mFileSystem.writev(fileDescriptor, addresses, offsets, lengths, size);
}
public long readDirect(int fileDescriptor, int address, int offset,
int length) throws IOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return mFileSystem.readDirect(fileDescriptor, address, offset, length);
}
public long writeDirect(int fileDescriptor, int address, int offset,
int length) throws IOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return mFileSystem.writeDirect(fileDescriptor, address, offset, length);
}
public boolean lock(int fileDescriptor, long start, long length, int type,
boolean waitFlag) throws IOException {
return mFileSystem.lock(fileDescriptor, start, length, type, waitFlag);
}
public void unlock(int fileDescriptor, long start, long length)
throws IOException {
mFileSystem.unlock(fileDescriptor, start, length);
}
public long seek(int fileDescriptor, long offset, int whence)
throws IOException {
return mFileSystem.seek(fileDescriptor, offset, whence);
}
public void fsync(int fileDescriptor, boolean metadata) throws IOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
mFileSystem.fsync(fileDescriptor, metadata);
}
public void truncate(int fileDescriptor, long size) throws IOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
mFileSystem.truncate(fileDescriptor, size);
}
public int getAllocGranularity() throws IOException {
return mFileSystem.getAllocGranularity();
}
public int open(String path, int mode) throws FileNotFoundException {
BlockGuard.getThreadPolicy().onReadFromDisk();
if (mode != 0) { // 0 is read-only
BlockGuard.getThreadPolicy().onWriteToDisk();
}
return mFileSystem.open(path, mode);
}
public long transfer(int fileHandler, FileDescriptor socketDescriptor,
long offset, long count) throws IOException {
return mFileSystem.transfer(fileHandler, socketDescriptor, offset, count);
}
public int ioctlAvailable(FileDescriptor fileDescriptor) throws IOException {
return mFileSystem.ioctlAvailable(fileDescriptor);
}
public long length(int fd) {
return mFileSystem.length(fd);
}
}
/**
* A network wrapper that calls the policy check functions.
*/
public static class WrappedNetworkSystem implements INetworkSystem {
private final INetworkSystem mNetwork;
public WrappedNetworkSystem(INetworkSystem network) {
mNetwork = network;
}
public void accept(FileDescriptor serverFd, SocketImpl newSocket,
FileDescriptor clientFd) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
mNetwork.accept(serverFd, newSocket, clientFd);
}
public void bind(FileDescriptor aFD, InetAddress inetAddress, int port)
throws SocketException {
mNetwork.bind(aFD, inetAddress, port);
}
public int read(FileDescriptor aFD, byte[] data, int offset, int count) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.read(aFD, data, offset, count);
}
public int readDirect(FileDescriptor aFD, int address, int count) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.readDirect(aFD, address, count);
}
public int write(FileDescriptor fd, byte[] data, int offset, int count)
throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.write(fd, data, offset, count);
}
public int writeDirect(FileDescriptor fd, int address, int offset, int count)
throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.writeDirect(fd, address, offset, count);
}
public boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.connectNonBlocking(fd, inetAddress, port);
}
public boolean isConnected(FileDescriptor fd, int timeout) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.isConnected(fd, timeout);
}
public int send(FileDescriptor fd, byte[] data, int offset, int length,
int port, InetAddress inetAddress) throws IOException {
// Note: no BlockGuard violation. We permit datagrams
// without hostname lookups. (short, bounded amount of time)
return mNetwork.send(fd, data, offset, length, port, inetAddress);
}
public int sendDirect(FileDescriptor fd, int address, int offset, int length,
int port, InetAddress inetAddress) throws IOException {
// Note: no BlockGuard violation. We permit datagrams
// without hostname lookups. (short, bounded amount of time)
return mNetwork.sendDirect(fd, address, offset, length, port, inetAddress);
}
public int recv(FileDescriptor fd, DatagramPacket packet, byte[] data, int offset,
int length, boolean peek, boolean connected) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.recv(fd, packet, data, offset, length, peek, connected);
}
public int recvDirect(FileDescriptor fd, DatagramPacket packet, int address, int offset,
int length, boolean peek, boolean connected) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.recvDirect(fd, packet, address, offset, length, peek, connected);
}
public void disconnectDatagram(FileDescriptor aFD) throws SocketException {
mNetwork.disconnectDatagram(aFD);
}
public void socket(FileDescriptor aFD, boolean stream) throws SocketException {
mNetwork.socket(aFD, stream);
}
public void shutdownInput(FileDescriptor descriptor) throws IOException {
mNetwork.shutdownInput(descriptor);
}
public void shutdownOutput(FileDescriptor descriptor) throws IOException {
mNetwork.shutdownOutput(descriptor);
}
public void sendUrgentData(FileDescriptor fd, byte value) {
mNetwork.sendUrgentData(fd, value);
}
public void listen(FileDescriptor aFD, int backlog) throws SocketException {
mNetwork.listen(aFD, backlog);
}
public void connect(FileDescriptor aFD, InetAddress inetAddress, int port,
int timeout) throws SocketException {
BlockGuard.getThreadPolicy().onNetwork();
mNetwork.connect(aFD, inetAddress, port, timeout);
}
public InetAddress getSocketLocalAddress(FileDescriptor aFD) {
return mNetwork.getSocketLocalAddress(aFD);
}
public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
int numReadable, int numWritable, long timeout, int[] flags)
throws SocketException {
BlockGuard.getThreadPolicy().onNetwork();
return mNetwork.select(readFDs, writeFDs, numReadable, numWritable, timeout, flags);
}
public int getSocketLocalPort(FileDescriptor aFD) {
return mNetwork.getSocketLocalPort(aFD);
}
public Object getSocketOption(FileDescriptor aFD, int opt)
throws SocketException {
return mNetwork.getSocketOption(aFD, opt);
}
public void setSocketOption(FileDescriptor aFD, int opt, Object optVal)
throws SocketException {
mNetwork.setSocketOption(aFD, opt, optVal);
}
public void close(FileDescriptor aFD) throws IOException {
// We exclude sockets without SO_LINGER so that apps can close their network connections
// in methods like onDestroy, which will run on the UI thread, without jumping through
// extra hoops.
if (isLingerSocket(aFD)) {
BlockGuard.getThreadPolicy().onNetwork();
}
mNetwork.close(aFD);
}
public void setInetAddress(InetAddress sender, byte[] address) {
mNetwork.setInetAddress(sender, address);
}
private boolean isLingerSocket(FileDescriptor fd) throws SocketException {
try {
Object lingerValue = mNetwork.getSocketOption(fd, SocketOptions.SO_LINGER);
if (lingerValue instanceof Boolean) {
return (Boolean) lingerValue;
} else if (lingerValue instanceof Integer) {
return ((Integer) lingerValue) != 0;
}
throw new AssertionError(lingerValue.getClass().getName());
} catch (Exception ignored) {
// We're called via Socket.close (which doesn't ask for us to be called), so we
// must not throw here, because Socket.close must not throw if asked to close an
// already-closed socket.
return false;
}
}
}
}