blob: 2d60a8690e286fd5dc85293778418f36fae73e7b [file] [log] [blame]
/*
* 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 libcore.io;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.BindException;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.PortUnreachableException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static libcore.io.OsConstants.*;
import libcore.util.MutableInt;
/**
* Implements java.io/java.net/java.nio semantics in terms of the underlying POSIX system calls.
*/
public final class IoBridge {
private IoBridge() {
}
public static int available(FileDescriptor fd) throws IOException {
try {
MutableInt available = new MutableInt(0);
Libcore.os.ioctlInt(fd, FIONREAD, available);
if (available.value < 0) {
// If the fd refers to a regular file, the result is the difference between
// the file size and the file position. This may be negative if the position
// is past the end of the file. If the fd refers to a special file masquerading
// as a regular file, the result may be negative because the special file
// may appear to have zero size and yet a previous read call may have
// read some amount of data and caused the file position to be advanced.
available.value = 0;
}
return available.value;
} catch (ErrnoException errnoException) {
if (errnoException.errno == ENOTTY) {
// The fd is unwilling to opine about its read buffer.
return 0;
}
throw errnoException.rethrowAsIOException();
}
}
public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException {
if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) {
// Linux won't let you bind a link-local address without a scope id. Find one.
NetworkInterface nif = NetworkInterface.getByInetAddress(address);
if (nif == null) {
throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
}
try {
address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
} catch (UnknownHostException ex) {
throw new AssertionError(ex); // Can't happen.
}
}
try {
Libcore.os.bind(fd, address, port);
} catch (ErrnoException errnoException) {
throw new BindException(errnoException.getMessage(), errnoException);
}
}
/**
* Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout
* means this method won't throw SocketTimeoutException.
*/
public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
try {
return IoBridge.connect(fd, inetAddress, port, 0);
} catch (SocketTimeoutException ex) {
throw new AssertionError(ex); // Can't happen for a connect without a timeout.
}
}
/**
* Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'.
* Use timeoutMs == 0 for a blocking connect with no timeout.
*/
public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
try {
return connectErrno(fd, inetAddress, port, timeoutMs);
} catch (ErrnoException errnoException) {
throw new ConnectException(connectDetail(inetAddress, port, timeoutMs, errnoException), errnoException);
} catch (SocketException ex) {
throw ex; // We don't want to doubly wrap these.
} catch (SocketTimeoutException ex) {
throw ex; // We don't want to doubly wrap these.
} catch (IOException ex) {
throw new SocketException(ex);
}
}
private static boolean connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException {
// With no timeout, just call connect(2) directly.
if (timeoutMs == 0) {
Libcore.os.connect(fd, inetAddress, port);
return true;
}
// For connect with a timeout, we:
// 1. set the socket to non-blocking,
// 2. connect(2),
// 3. loop using poll(2) to decide whether we're connected, whether we should keep
// waiting, or whether we've seen a permanent failure and should give up,
// 4. set the socket back to blocking.
// 1. set the socket to non-blocking.
IoUtils.setBlocking(fd, false);
// 2. call connect(2) non-blocking.
long finishTimeMs = System.currentTimeMillis() + timeoutMs;
try {
Libcore.os.connect(fd, inetAddress, port);
IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking.
return true; // We connected immediately.
} catch (ErrnoException errnoException) {
if (errnoException.errno != EINPROGRESS) {
throw errnoException;
}
// EINPROGRESS means we should keep trying...
}
// 3. loop using poll(2).
int remainingTimeoutMs;
do {
remainingTimeoutMs = (int) (finishTimeMs - System.currentTimeMillis());
if (remainingTimeoutMs <= 0) {
throw new SocketTimeoutException(connectDetail(inetAddress, port, timeoutMs, null));
}
} while (!IoBridge.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs));
IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking.
return true; // Or we'd have thrown.
}
private static String connectDetail(InetAddress inetAddress, int port, int timeoutMs, ErrnoException cause) {
String detail = "failed to connect to " + inetAddress + " (port " + port + ")";
if (timeoutMs > 0) {
detail += " after " + timeoutMs + "ms";
}
if (cause != null) {
detail += ": " + cause.getMessage();
}
return detail;
}
public static void closeSocket(FileDescriptor fd) throws IOException {
if (!fd.valid()) {
// Socket.close doesn't throw if you try to close an already-closed socket.
return;
}
int intFd = fd.getInt$();
fd.setInt$(-1);
FileDescriptor oldFd = new FileDescriptor();
oldFd.setInt$(intFd);
AsynchronousCloseMonitor.signalBlockedThreads(oldFd);
try {
Libcore.os.close(oldFd);
} catch (ErrnoException errnoException) {
// TODO: are there any cases in which we should throw?
}
}
public static boolean isConnected(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs, int remainingTimeoutMs) throws IOException {
ErrnoException cause;
try {
StructPollfd[] pollFds = new StructPollfd[] { new StructPollfd() };
pollFds[0].fd = fd;
pollFds[0].events = (short) POLLOUT;
int rc = Libcore.os.poll(pollFds, remainingTimeoutMs);
if (rc == 0) {
return false; // Timeout.
}
int connectError = Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_ERROR);
if (connectError == 0) {
return true; // Success!
}
throw new ErrnoException("isConnected", connectError); // The connect(2) failed.
} catch (ErrnoException errnoException) {
if (!fd.valid()) {
throw new SocketException("Socket closed");
}
if (errnoException.errno == EINTR) {
return false; // Punt and ask the caller to try again.
} else {
cause = errnoException;
}
}
// TODO: is it really helpful/necessary to throw so many different exceptions?
String detail = connectDetail(inetAddress, port, timeoutMs, cause);
if (cause.errno == ECONNRESET || cause.errno == ECONNREFUSED ||
cause.errno == EADDRNOTAVAIL || cause.errno == EADDRINUSE ||
cause.errno == ENETUNREACH) {
throw new ConnectException(detail, cause);
} else if (cause.errno == EACCES) {
throw new SecurityException(detail, cause);
} else if (cause.errno == ETIMEDOUT) {
throw new SocketTimeoutException(detail, cause);
}
throw new SocketException(detail, cause);
}
// Socket options used by java.net but not exposed in SocketOptions.
public static final int JAVA_MCAST_JOIN_GROUP = 19;
public static final int JAVA_MCAST_LEAVE_GROUP = 20;
public static final int JAVA_IP_MULTICAST_TTL = 17;
/**
* java.net has its own socket options similar to the underlying Unix ones. We paper over the
* differences here.
*/
public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException {
try {
return getSocketOptionErrno(fd, option);
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsSocketException();
}
}
private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws ErrnoException, SocketException {
switch (option) {
case SocketOptions.IP_MULTICAST_IF:
// This is IPv4-only.
return Libcore.os.getsockoptInAddr(fd, IPPROTO_IP, IP_MULTICAST_IF);
case SocketOptions.IP_MULTICAST_IF2:
// This is IPv6-only.
return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
case SocketOptions.IP_MULTICAST_LOOP:
// Since setting this from java.net always sets IPv4 and IPv6 to the same value,
// it doesn't matter which we return.
return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
case IoBridge.JAVA_IP_MULTICAST_TTL:
// Since setting this from java.net always sets IPv4 and IPv6 to the same value,
// it doesn't matter which we return.
return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
case SocketOptions.IP_TOS:
// Since setting this from java.net always sets IPv4 and IPv6 to the same value,
// it doesn't matter which we return.
return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
case SocketOptions.SO_BROADCAST:
return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_BROADCAST));
case SocketOptions.SO_KEEPALIVE:
return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE));
case SocketOptions.SO_LINGER:
StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
if (!linger.isOn()) {
return false;
}
return linger.l_linger;
case SocketOptions.SO_OOBINLINE:
return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE));
case SocketOptions.SO_RCVBUF:
return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
case SocketOptions.SO_REUSEADDR:
return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR));
case SocketOptions.SO_SNDBUF:
return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
case SocketOptions.SO_TIMEOUT:
return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis();
case SocketOptions.TCP_NODELAY:
return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
default:
throw new SocketException("Unknown socket option: " + option);
}
}
private static boolean booleanFromInt(int i) {
return (i != 0);
}
private static int booleanToInt(boolean b) {
return b ? 1 : 0;
}
/**
* java.net has its own socket options similar to the underlying Unix ones. We paper over the
* differences here.
*/
public static void setSocketOption(FileDescriptor fd, int option, Object value) throws SocketException {
try {
setSocketOptionErrno(fd, option, value);
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsSocketException();
}
}
private static void setSocketOptionErrno(FileDescriptor fd, int option, Object value) throws ErrnoException, SocketException {
switch (option) {
case SocketOptions.IP_MULTICAST_IF:
throw new UnsupportedOperationException("Use IP_MULTICAST_IF2 on Android");
case SocketOptions.IP_MULTICAST_IF2:
// Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, (Integer) value);
Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (Integer) value);
return;
case SocketOptions.IP_MULTICAST_LOOP:
// Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, booleanToInt((Boolean) value));
Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, booleanToInt((Boolean) value));
return;
case IoBridge.JAVA_IP_MULTICAST_TTL:
// Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
// IPv4 multicast TTL uses a byte.
Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL, (Integer) value);
Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (Integer) value);
return;
case SocketOptions.IP_TOS:
Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_TOS, (Integer) value);
Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS, (Integer) value);
return;
case SocketOptions.SO_BROADCAST:
Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_BROADCAST, booleanToInt((Boolean) value));
return;
case SocketOptions.SO_KEEPALIVE:
Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE, booleanToInt((Boolean) value));
return;
case SocketOptions.SO_LINGER:
boolean on = false;
int seconds = 0;
if (value instanceof Integer) {
on = true;
seconds = Math.min((Integer) value, 65535);
}
StructLinger linger = new StructLinger(booleanToInt(on), seconds);
Libcore.os.setsockoptLinger(fd, SOL_SOCKET, SO_LINGER, linger);
return;
case SocketOptions.SO_OOBINLINE:
Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE, booleanToInt((Boolean) value));
return;
case SocketOptions.SO_RCVBUF:
Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, (Integer) value);
return;
case SocketOptions.SO_REUSEADDR:
Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR, booleanToInt((Boolean) value));
return;
case SocketOptions.SO_SNDBUF:
Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_SNDBUF, (Integer) value);
return;
case SocketOptions.SO_TIMEOUT:
int millis = (Integer) value;
StructTimeval tv = StructTimeval.fromMillis(millis);
Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv);
return;
case SocketOptions.TCP_NODELAY:
Libcore.os.setsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY, booleanToInt((Boolean) value));
return;
case IoBridge.JAVA_MCAST_JOIN_GROUP:
case IoBridge.JAVA_MCAST_LEAVE_GROUP:
StructGroupReq groupReq = (StructGroupReq) value;
int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
return;
default:
throw new SocketException("Unknown socket option: " + option);
}
}
/**
* java.io only throws FileNotFoundException when opening files, regardless of what actually
* went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening
* directories: POSIX says read-only is okay, but java.io doesn't even allow that. We also
* have an Android-specific hack to alter the default permissions.
*/
public static FileDescriptor open(String path, int flags) throws FileNotFoundException {
FileDescriptor fd = null;
try {
// On Android, we don't want default permissions to allow global access.
int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600;
fd = Libcore.os.open(path, flags, mode);
if (fd.valid()) {
// Posix open(2) fails with EISDIR only if you ask for write permission.
// Java disallows reading directories too.
if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
throw new ErrnoException("open", EISDIR);
}
}
return fd;
} catch (ErrnoException errnoException) {
try {
if (fd != null) {
IoUtils.close(fd);
}
} catch (IOException ignored) {
}
FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage());
ex.initCause(errnoException);
throw ex;
}
}
/**
* java.io thinks that a read at EOF is an error and should return -1, contrary to traditional
* Unix practice where you'd read until you got 0 bytes (and any future read would return -1).
*/
public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
if (byteCount == 0) {
return 0;
}
try {
int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount);
if (readCount == 0) {
return -1;
}
return readCount;
} catch (ErrnoException errnoException) {
if (errnoException.errno == EAGAIN) {
// We return 0 rather than throw if we try to read from an empty non-blocking pipe.
return 0;
}
throw errnoException.rethrowAsIOException();
}
}
/**
* java.io always writes every byte it's asked to, or fails with an error. (That is, unlike
* Unix it never just writes as many bytes as happens to be convenient.)
*/
public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
if (byteCount == 0) {
return;
}
try {
while (byteCount > 0) {
int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount);
byteCount -= bytesWritten;
byteOffset += bytesWritten;
}
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsIOException();
}
}
public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws IOException {
boolean isDatagram = (inetAddress != null);
if (!isDatagram && byteCount <= 0) {
return 0;
}
int result;
try {
result = Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);
} catch (ErrnoException errnoException) {
result = maybeThrowAfterSendto(isDatagram, errnoException);
}
return result;
}
public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws IOException {
boolean isDatagram = (inetAddress != null);
if (!isDatagram && buffer.remaining() == 0) {
return 0;
}
int result;
try {
result = Libcore.os.sendto(fd, buffer, flags, inetAddress, port);
} catch (ErrnoException errnoException) {
result = maybeThrowAfterSendto(isDatagram, errnoException);
}
return result;
}
private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException) throws SocketException {
if (isDatagram) {
if (errnoException.errno == ECONNRESET || errnoException.errno == ECONNREFUSED) {
return 0;
}
} else {
if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
// We were asked to write to a non-blocking socket, but were told
// it would block, so report "no bytes written".
return 0;
}
}
throw errnoException.rethrowAsSocketException();
}
public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
int result;
try {
InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null;
result = Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);
result = postRecvfrom(isRead, packet, isConnected, srcAddress, result);
} catch (ErrnoException errnoException) {
result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
}
return result;
}
public static int recvfrom(boolean isRead, FileDescriptor fd, ByteBuffer buffer, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
int result;
try {
InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null;
result = Libcore.os.recvfrom(fd, buffer, flags, srcAddress);
result = postRecvfrom(isRead, packet, isConnected, srcAddress, result);
} catch (ErrnoException errnoException) {
result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
}
return result;
}
private static int postRecvfrom(boolean isRead, DatagramPacket packet, boolean isConnected, InetSocketAddress srcAddress, int byteCount) {
if (isRead && byteCount == 0) {
return -1;
}
if (packet != null) {
packet.setReceivedLength(byteCount);
if (!isConnected) {
packet.setAddress(srcAddress.getAddress());
packet.setPort(srcAddress.getPort());
}
}
return byteCount;
}
private static int maybeThrowAfterRecvfrom(boolean isRead, boolean isConnected, ErrnoException errnoException) throws SocketException, SocketTimeoutException {
if (isRead) {
if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
return 0;
} else {
throw errnoException.rethrowAsSocketException();
}
} else {
if (isConnected && errnoException.errno == ECONNREFUSED) {
throw new PortUnreachableException("", errnoException);
} else if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
throw new SocketTimeoutException(errnoException);
} else {
throw errnoException.rethrowAsSocketException();
}
}
}
public static FileDescriptor socket(boolean stream) throws SocketException {
FileDescriptor fd;
try {
fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
// The RFC (http://www.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults
// to 1. The Linux kernel (at least up to 2.6.38) accidentally defaults to 64 (which
// would be correct for the *unicast* hop limit).
// See http://www.spinics.net/lists/netdev/msg129022.html, though no patch appears to
// have been applied as a result of that discussion. If that bug is ever fixed, we can
// remove this code. Until then, we manually set the hop limit on IPv6 datagram sockets.
// (IPv4 is already correct.)
if (!stream) {
Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 1);
}
return fd;
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsSocketException();
}
}
public static InetAddress getSocketLocalAddress(FileDescriptor fd) {
try {
SocketAddress sa = Libcore.os.getsockname(fd);
InetSocketAddress isa = (InetSocketAddress) sa;
return isa.getAddress();
} catch (ErrnoException errnoException) {
throw new AssertionError(errnoException);
}
}
public static int getSocketLocalPort(FileDescriptor fd) {
try {
SocketAddress sa = Libcore.os.getsockname(fd);
InetSocketAddress isa = (InetSocketAddress) sa;
return isa.getPort();
} catch (ErrnoException errnoException) {
throw new AssertionError(errnoException);
}
}
}