| /* VMChannel.java -- Native interface suppling channel operations. |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.java.nio; |
| |
| import gnu.classpath.Configuration; |
| |
| import java.io.IOException; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.SocketAddress; |
| import java.net.SocketException; |
| import java.nio.ByteBuffer; |
| import java.nio.MappedByteBuffer; |
| |
| /** |
| * Native interface to support configuring of channel to run in a non-blocking |
| * manner and support scatter/gather io operations. |
| * |
| * @author Michael Barker <mike@middlesoft.co.uk> |
| * |
| */ |
| public final class VMChannel |
| { |
| /** |
| * Our reference implementation uses an integer to store the native |
| * file descriptor. Implementations without such support |
| */ |
| private final State nfd; |
| |
| private Kind kind; |
| |
| public VMChannel() |
| { |
| // XXX consider adding security check here, so only Classpath |
| // code may create instances. |
| this.nfd = new State(); |
| kind = Kind.OTHER; |
| } |
| |
| /** |
| * This constructor is used by the POSIX reference implementation; |
| * other virtual machines need not support it. |
| * |
| * <strong>Important:</strong> do not call this in library code that is |
| * not specific to Classpath's reference implementation. |
| * |
| * @param native_fd The native file descriptor integer. |
| * @throws IOException |
| */ |
| VMChannel(final int native_fd) throws IOException |
| { |
| this(); |
| this.nfd.setNativeFD(native_fd); |
| } |
| |
| public State getState() |
| { |
| return nfd; |
| } |
| |
| static |
| { |
| // load the shared library needed for native methods. |
| if (Configuration.INIT_LOAD_LIBRARY) |
| { |
| System.loadLibrary ("javanio"); |
| } |
| initIDs(); |
| } |
| |
| public static VMChannel getStdin() throws IOException |
| { |
| return new VMChannel(stdin_fd()); |
| } |
| |
| public static VMChannel getStdout() throws IOException |
| { |
| return new VMChannel(stdout_fd()); |
| } |
| |
| public static VMChannel getStderr() throws IOException |
| { |
| return new VMChannel(stderr_fd()); |
| } |
| |
| private static native int stdin_fd(); |
| private static native int stdout_fd(); |
| private static native int stderr_fd(); |
| |
| /** |
| * Set the file descriptor to have the required blocking |
| * setting. |
| * |
| * @param blocking The blocking flag to set. |
| */ |
| public void setBlocking(boolean blocking) throws IOException |
| { |
| setBlocking(nfd.getNativeFD(), blocking); |
| } |
| |
| private static native void setBlocking(int fd, boolean blocking) |
| throws IOException; |
| |
| public int available() throws IOException |
| { |
| return available(nfd.getNativeFD()); |
| } |
| |
| private static native int available(int native_fd) throws IOException; |
| |
| /** |
| * Reads a byte buffer directly using the supplied file descriptor. |
| * |
| * @param dst Direct Byte Buffer to read to. |
| * @return Number of bytes read. |
| * @throws IOException If an error occurs or dst is not a direct buffers. |
| */ |
| public int read(ByteBuffer dst) |
| throws IOException |
| { |
| return read(nfd.getNativeFD(), dst); |
| } |
| |
| private static native int read(int fd, ByteBuffer dst) throws IOException; |
| |
| /** |
| * Read a single byte. |
| * |
| * @return The byte read, or -1 on end of file. |
| * @throws IOException |
| */ |
| public int read() throws IOException |
| { |
| return read(nfd.getNativeFD()); |
| } |
| |
| private static native int read(int fd) throws IOException; |
| |
| /** |
| * Reads into byte buffers directly using the supplied file descriptor. |
| * Assumes that the buffer list contains DirectBuffers. Will perform a |
| * scattering read. |
| * |
| * @param dsts An array direct byte buffers. |
| * @param offset Index of the first buffer to read to. |
| * @param length The number of buffers to read to. |
| * @return Number of bytes read. |
| * @throws IOException If an error occurs or the dsts are not direct buffers. |
| */ |
| public long readScattering(ByteBuffer[] dsts, int offset, int length) |
| throws IOException |
| { |
| if (offset + length > dsts.length) |
| throw new IndexOutOfBoundsException("offset + length > dsts.length"); |
| |
| return readScattering(nfd.getNativeFD(), dsts, offset, length); |
| } |
| |
| private static native long readScattering(int fd, ByteBuffer[] dsts, |
| int offset, int length) |
| throws IOException; |
| |
| /** |
| * Receive a datagram on this channel, returning the host address |
| * that sent the datagram. |
| * |
| * @param dst Where to store the datagram. |
| * @return The host address that sent the datagram. |
| * @throws IOException |
| */ |
| public SocketAddress receive(ByteBuffer dst) throws IOException |
| { |
| if (kind != Kind.SOCK_DGRAM) |
| throw new SocketException("not a datagram socket"); |
| ByteBuffer hostPort = ByteBuffer.allocateDirect(18); |
| int hostlen = receive(nfd.getNativeFD(), dst, hostPort); |
| if (hostlen == 0) |
| return null; |
| if (hostlen == 4) // IPv4 |
| { |
| byte[] addr = new byte[4]; |
| hostPort.get(addr); |
| int port = hostPort.getShort() & 0xFFFF; |
| return new InetSocketAddress(Inet4Address.getByAddress(addr), port); |
| } |
| if (hostlen == 16) // IPv6 |
| { |
| byte[] addr = new byte[16]; |
| hostPort.get(addr); |
| int port = hostPort.getShort() & 0xFFFF; |
| return new InetSocketAddress(Inet6Address.getByAddress(addr), port); |
| } |
| |
| throw new SocketException("host address received with invalid length: " |
| + hostlen); |
| } |
| |
| private static native int receive (int fd, ByteBuffer dst, ByteBuffer address) |
| throws IOException; |
| |
| /** |
| * Writes from a direct byte bufer using the supplied file descriptor. |
| * Assumes the buffer is a DirectBuffer. |
| * |
| * @param src The source buffer. |
| * @return Number of bytes written. |
| * @throws IOException |
| */ |
| public int write(ByteBuffer src) throws IOException |
| { |
| return write(nfd.getNativeFD(), src); |
| } |
| |
| private native int write(int fd, ByteBuffer src) throws IOException; |
| |
| /** |
| * Writes from byte buffers directly using the supplied file descriptor. |
| * Assumes the that buffer list constains DirectBuffers. Will perform |
| * as gathering write. |
| * |
| * @param srcs |
| * @param offset |
| * @param length |
| * @return Number of bytes written. |
| * @throws IOException |
| */ |
| public long writeGathering(ByteBuffer[] srcs, int offset, int length) |
| throws IOException |
| { |
| if (offset + length > srcs.length) |
| throw new IndexOutOfBoundsException("offset + length > srcs.length"); |
| |
| // A gathering write is limited to 16 buffers; when writing, ensure |
| // that we have at least one buffer with something in it in the 16 |
| // buffer window starting at offset. |
| while (!srcs[offset].hasRemaining() && offset < srcs.length) |
| offset++; |
| |
| // There are no buffers with anything to write. |
| if (offset == srcs.length) |
| return 0; |
| |
| // If we advanced `offset' so far that we don't have `length' |
| // buffers left, reset length to only the remaining buffers. |
| if (length > srcs.length - offset) |
| length = srcs.length - offset; |
| |
| return writeGathering(nfd.getNativeFD(), srcs, offset, length); |
| } |
| |
| private native long writeGathering(int fd, ByteBuffer[] srcs, |
| int offset, int length) |
| throws IOException; |
| |
| /** |
| * Send a datagram to the given address. |
| * |
| * @param src The source buffer. |
| * @param dst The destination address. |
| * @return The number of bytes written. |
| * @throws IOException |
| */ |
| public int send(ByteBuffer src, InetSocketAddress dst) |
| throws IOException |
| { |
| InetAddress addr = dst.getAddress(); |
| if (addr == null) |
| throw new NullPointerException(); |
| if (addr instanceof Inet4Address) |
| return send(nfd.getNativeFD(), src, addr.getAddress(), dst.getPort()); |
| else if (addr instanceof Inet6Address) |
| return send6(nfd.getNativeFD(), src, addr.getAddress(), dst.getPort()); |
| else |
| throw new SocketException("unrecognized inet address type"); |
| } |
| |
| // Send to an IPv4 address. |
| private static native int send(int fd, ByteBuffer src, byte[] addr, int port) |
| throws IOException; |
| |
| // Send to an IPv6 address. |
| private static native int send6(int fd, ByteBuffer src, byte[] addr, int port) |
| throws IOException; |
| |
| /** |
| * Write a single byte. |
| * |
| * @param b The byte to write. |
| * @throws IOException |
| */ |
| public void write(int b) throws IOException |
| { |
| write(nfd.getNativeFD(), b); |
| } |
| |
| private static native void write(int fd, int b) throws IOException; |
| |
| private native static void initIDs(); |
| |
| // Network (socket) specific methods. |
| |
| /** |
| * Create a new socket. This method will initialize the native file |
| * descriptor state of this instance. |
| * |
| * @param stream Whether or not to create a streaming socket, or a datagram |
| * socket. |
| * @throws IOException If creating a new socket fails, or if this |
| * channel already has its native descriptor initialized. |
| */ |
| public void initSocket(boolean stream) throws IOException |
| { |
| if (nfd.isValid()) |
| throw new IOException("native FD already initialized"); |
| if (stream) |
| kind = Kind.SOCK_STREAM; |
| else |
| kind = Kind.SOCK_DGRAM; |
| nfd.setNativeFD(socket(stream)); |
| } |
| |
| /** |
| * Create a new socket, returning the native file descriptor. |
| * |
| * @param stream Set to true for streaming sockets; false for datagrams. |
| * @return The native file descriptor. |
| * @throws IOException If creating the socket fails. |
| */ |
| private static native int socket(boolean stream) throws IOException; |
| |
| /** |
| * Connect the underlying socket file descriptor to the remote host. |
| * |
| * @param saddr The address to connect to. |
| * @param timeout The connect timeout to use for blocking connects. |
| * @return True if the connection succeeded; false if the file descriptor |
| * is in non-blocking mode and the connection did not immediately |
| * succeed. |
| * @throws IOException If an error occurs while connecting. |
| */ |
| public boolean connect(InetSocketAddress saddr, int timeout) |
| throws SocketException |
| { |
| int fd; |
| |
| InetAddress addr = saddr.getAddress(); |
| |
| // Translates an IOException into a SocketException to conform |
| // to the throws clause. |
| try |
| { |
| fd = nfd.getNativeFD(); |
| } |
| catch (IOException ioe) |
| { |
| throw new SocketException(ioe.getMessage()); |
| } |
| |
| if (addr instanceof Inet4Address) |
| return connect(fd, addr.getAddress(), saddr.getPort(), |
| timeout); |
| if (addr instanceof Inet6Address) |
| return connect6(fd, addr.getAddress(), saddr.getPort(), |
| timeout); |
| throw new SocketException("unsupported internet address"); |
| } |
| |
| private static native boolean connect(int fd, byte[] addr, int port, int timeout) |
| throws SocketException; |
| |
| private static native boolean connect6(int fd, byte[] addr, int port, int timeout) |
| throws SocketException; |
| |
| /** |
| * Disconnect this channel, if it is a datagram socket. Disconnecting |
| * a datagram channel will disassociate it from any address, so the |
| * socket will remain open, but can send and receive datagrams from |
| * any address. |
| * |
| * @throws IOException If disconnecting this channel fails, or if this |
| * channel is not a datagram channel. |
| */ |
| public void disconnect() throws IOException |
| { |
| if (kind != Kind.SOCK_DGRAM) |
| throw new IOException("can only disconnect datagram channels"); |
| disconnect(nfd.getNativeFD()); |
| } |
| |
| private static native void disconnect(int fd) throws IOException; |
| |
| public InetSocketAddress getLocalAddress() throws IOException |
| { |
| if (!nfd.isValid()) |
| return null; |
| ByteBuffer name = ByteBuffer.allocateDirect(18); |
| int namelen = getsockname(nfd.getNativeFD(), name); |
| if (namelen == 0) // not bound |
| return null; // XXX return some wildcard? |
| if (namelen == 4) |
| { |
| byte[] addr = new byte[4]; |
| name.get(addr); |
| int port = name.getShort() & 0xFFFF; |
| return new InetSocketAddress(Inet4Address.getByAddress(addr), port); |
| } |
| if (namelen == 16) |
| { |
| byte[] addr = new byte[16]; |
| name.get(addr); |
| int port = name.getShort() & 0xFFFF; |
| return new InetSocketAddress(Inet6Address.getByAddress(addr), port); |
| } |
| throw new SocketException("invalid address length"); |
| } |
| |
| private static native int getsockname(int fd, ByteBuffer name) |
| throws IOException; |
| |
| /** |
| * Returns the socket address of the remote peer this channel is connected |
| * to, or null if this channel is not yet connected. |
| * |
| * @return The peer address. |
| * @throws IOException |
| */ |
| public InetSocketAddress getPeerAddress() throws IOException |
| { |
| if (!nfd.isValid()) |
| return null; |
| ByteBuffer name = ByteBuffer.allocateDirect(18); |
| int namelen = getpeername (nfd.getNativeFD(), name); |
| if (namelen == 0) // not connected yet |
| return null; |
| if (namelen == 4) // IPv4 |
| { |
| byte[] addr = new byte[4]; |
| name.get(addr); |
| int port = name.getShort() & 0xFFFF; |
| return new InetSocketAddress(Inet4Address.getByAddress(addr), port); |
| } |
| else if (namelen == 16) // IPv6 |
| { |
| byte[] addr = new byte[16]; |
| name.get(addr); |
| int port = name.getShort() & 0xFFFF; |
| return new InetSocketAddress(Inet6Address.getByAddress(addr), port); |
| } |
| throw new SocketException("invalid address length"); |
| } |
| |
| /* |
| * The format here is the peer address, followed by the port number. |
| * The returned value is the length of the peer address; thus, there |
| * will be LEN + 2 valid bytes put into NAME. |
| */ |
| private static native int getpeername(int fd, ByteBuffer name) |
| throws IOException; |
| |
| /** |
| * Accept an incoming connection, returning a new VMChannel, or null |
| * if the channel is nonblocking and no connection is pending. |
| * |
| * @return The accepted connection, or null. |
| * @throws IOException If an IO error occurs. |
| */ |
| public VMChannel accept() throws IOException |
| { |
| int new_fd = accept(nfd.getNativeFD()); |
| if (new_fd == -1) // non-blocking accept had no pending connection |
| return null; |
| return new VMChannel(new_fd); |
| } |
| |
| private static native int accept(int native_fd) throws IOException; |
| |
| // File-specific methods. |
| |
| /** |
| * Open a file at PATH, initializing the native state to operate on |
| * that open file. |
| * |
| * @param path The absolute file path. |
| * @throws IOException If the file cannot be opened, or if this |
| * channel was previously initialized. |
| */ |
| public void openFile(String path, int mode) throws IOException |
| { |
| if (nfd.isValid() || nfd.isClosed()) |
| throw new IOException("can't reinitialize this channel"); |
| int fd = open(path, mode); |
| nfd.setNativeFD(fd); |
| kind = Kind.FILE; |
| } |
| |
| private static native int open(String path, int mode) throws IOException; |
| |
| public long position() throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| return position(nfd.getNativeFD()); |
| } |
| |
| private static native long position(int fd) throws IOException; |
| |
| public void seek(long pos) throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| seek(nfd.getNativeFD(), pos); |
| } |
| |
| private static native void seek(int fd, long pos) throws IOException; |
| |
| public void truncate(long length) throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| truncate(nfd.getNativeFD(), length); |
| } |
| |
| private static native void truncate(int fd, long len) throws IOException; |
| |
| public boolean lock(long pos, long len, boolean shared, boolean wait) |
| throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| return lock(nfd.getNativeFD(), pos, len, shared, wait); |
| } |
| |
| private static native boolean lock(int fd, long pos, long len, |
| boolean shared, boolean wait) |
| throws IOException; |
| |
| public void unlock(long pos, long len) throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| unlock(nfd.getNativeFD(), pos, len); |
| } |
| |
| private static native void unlock(int fd, long pos, long len) throws IOException; |
| |
| public long size() throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| return size(nfd.getNativeFD()); |
| } |
| |
| private static native long size(int fd) throws IOException; |
| |
| public MappedByteBuffer map(char mode, long position, int size) |
| throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| return map(nfd.getNativeFD(), mode, position, size); |
| } |
| |
| private static native MappedByteBuffer map(int fd, char mode, |
| long position, int size) |
| throws IOException; |
| |
| public boolean flush(boolean metadata) throws IOException |
| { |
| if (kind != Kind.FILE) |
| throw new IOException("not a file"); |
| return flush(nfd.getNativeFD(), metadata); |
| } |
| |
| private static native boolean flush(int fd, boolean metadata) throws IOException; |
| |
| // Close. |
| |
| /** |
| * Close this socket. The socket is also automatically closed when this |
| * object is finalized. |
| * |
| * @throws IOException If closing the socket fails, or if this object has |
| * no open socket. |
| */ |
| public void close() throws IOException |
| { |
| nfd.close(); |
| } |
| |
| static native void close(int native_fd) throws IOException; |
| |
| /** |
| * <p>Provides a simple mean for the JNI code to find out whether the |
| * current thread was interrupted by a call to Thread.interrupt().</p> |
| * |
| * @return |
| */ |
| static boolean isThreadInterrupted() |
| { |
| return Thread.currentThread().isInterrupted(); |
| } |
| |
| // Inner classes. |
| |
| /** |
| * A wrapper for a native file descriptor integer. This tracks the state |
| * of an open file descriptor, and ensures that |
| * |
| * This class need not be fully supported by virtual machines; if a |
| * virtual machine does not use integer file descriptors, or does and |
| * wishes to hide that, then the methods of this class may be stubbed out. |
| * |
| * System-specific classes that depend on access to native file descriptor |
| * integers SHOULD declare this fact. |
| */ |
| public final class State |
| { |
| private int native_fd; |
| private boolean valid; |
| private boolean closed; |
| |
| State() |
| { |
| native_fd = -1; |
| valid = false; |
| closed = false; |
| } |
| |
| public boolean isValid() |
| { |
| return valid; |
| } |
| |
| public boolean isClosed() |
| { |
| return closed; |
| } |
| |
| public int getNativeFD() throws IOException |
| { |
| if (!valid) |
| throw new IOException("invalid file descriptor"); |
| return native_fd; |
| } |
| |
| void setNativeFD(final int native_fd) throws IOException |
| { |
| if (valid) |
| throw new IOException("file descriptor already initialized"); |
| this.native_fd = native_fd; |
| valid = true; |
| } |
| |
| public void close() throws IOException |
| { |
| if (!valid) |
| throw new IOException("invalid file descriptor"); |
| try |
| { |
| VMChannel.close(native_fd); |
| } |
| finally |
| { |
| valid = false; |
| closed = true; |
| } |
| } |
| |
| public String toString() |
| { |
| if (closed) |
| return "<<closed>>"; |
| if (!valid) |
| return "<<invalid>>"; |
| return String.valueOf(native_fd); |
| } |
| |
| protected void finalize() throws Throwable |
| { |
| try |
| { |
| if (valid) |
| close(); |
| } |
| finally |
| { |
| super.finalize(); |
| } |
| } |
| } |
| |
| /** |
| * An enumeration of possible kinds of channel. |
| */ |
| static class Kind // XXX enum |
| { |
| /** A streaming (TCP) socket. */ |
| static final Kind SOCK_STREAM = new Kind(); |
| |
| /** A datagram (UDP) socket. */ |
| static final Kind SOCK_DGRAM = new Kind(); |
| |
| /** A file. */ |
| static final Kind FILE = new Kind(); |
| |
| /** Something else; not a socket or file. */ |
| static final Kind OTHER = new Kind(); |
| |
| private Kind() { } |
| } |
| } |