1 /* VMChannel.java -- Native interface suppling channel operations.
2    Copyright (C) 2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package gnu.java.nio;
40 
41 import gnu.classpath.Configuration;
42 
43 import java.io.IOException;
44 import java.net.Inet4Address;
45 import java.net.Inet6Address;
46 import java.net.InetAddress;
47 import java.net.InetSocketAddress;
48 import java.net.SocketAddress;
49 import java.net.SocketException;
50 import java.nio.ByteBuffer;
51 import java.nio.MappedByteBuffer;
52 
53 /**
54  * Native interface to support configuring of channel to run in a non-blocking
55  * manner and support scatter/gather io operations.
56  *
57  * @author Michael Barker <mike@middlesoft.co.uk>
58  *
59  */
60 public final class VMChannel
61 {
62   /**
63    * Our reference implementation uses an integer to store the native
64    * file descriptor. Implementations without such support
65    */
66   private final State nfd;
67 
68   private Kind kind;
69 
VMChannel()70   public VMChannel()
71   {
72     // XXX consider adding security check here, so only Classpath
73     // code may create instances.
74     this.nfd = new State();
75     kind = Kind.OTHER;
76   }
77 
78   /**
79    * This constructor is used by the POSIX reference implementation;
80    * other virtual machines need not support it.
81    *
82    * <strong>Important:</strong> do not call this in library code that is
83    * not specific to Classpath's reference implementation.
84    *
85    * @param native_fd The native file descriptor integer.
86    * @throws IOException
87    */
VMChannel(final int native_fd)88   VMChannel(final int native_fd) throws IOException
89   {
90     this();
91     this.nfd.setNativeFD(native_fd);
92   }
93 
getState()94   public State getState()
95   {
96     return nfd;
97   }
98 
99   static
100   {
101     // load the shared library needed for native methods.
102     if (Configuration.INIT_LOAD_LIBRARY)
103       {
104         System.loadLibrary ("javanio");
105       }
initIDs()106     initIDs();
107   }
108 
getStdin()109   public static VMChannel getStdin() throws IOException
110   {
111     return new VMChannel(stdin_fd());
112   }
113 
getStdout()114   public static VMChannel getStdout() throws IOException
115   {
116     return new VMChannel(stdout_fd());
117   }
118 
getStderr()119   public static VMChannel getStderr() throws IOException
120   {
121     return new VMChannel(stderr_fd());
122   }
123 
stdin_fd()124   private static native int stdin_fd();
stdout_fd()125   private static native int stdout_fd();
stderr_fd()126   private static native int stderr_fd();
127 
128   /**
129    * Set the file descriptor to have the required blocking
130    * setting.
131    *
132    * @param blocking The blocking flag to set.
133    */
setBlocking(boolean blocking)134   public void setBlocking(boolean blocking) throws IOException
135   {
136     setBlocking(nfd.getNativeFD(), blocking);
137   }
138 
setBlocking(int fd, boolean blocking)139   private static native void setBlocking(int fd, boolean blocking)
140     throws IOException;
141 
available()142   public int available() throws IOException
143   {
144     return available(nfd.getNativeFD());
145   }
146 
available(int native_fd)147   private static native int available(int native_fd) throws IOException;
148 
149   /**
150    * Reads a byte buffer directly using the supplied file descriptor.
151    *
152    * @param dst Direct Byte Buffer to read to.
153    * @return Number of bytes read.
154    * @throws IOException If an error occurs or dst is not a direct buffers.
155    */
read(ByteBuffer dst)156   public int read(ByteBuffer dst)
157     throws IOException
158   {
159     return read(nfd.getNativeFD(), dst);
160   }
161 
read(int fd, ByteBuffer dst)162   private static native int read(int fd, ByteBuffer dst) throws IOException;
163 
164   /**
165    * Read a single byte.
166    *
167    * @return The byte read, or -1 on end of file.
168    * @throws IOException
169    */
read()170   public int read() throws IOException
171   {
172     return read(nfd.getNativeFD());
173   }
174 
read(int fd)175   private static native int read(int fd) throws IOException;
176 
177   /**
178    * Reads into byte buffers directly using the supplied file descriptor.
179    * Assumes that the buffer list contains DirectBuffers.  Will perform a
180    * scattering read.
181    *
182    * @param dsts An array direct byte buffers.
183    * @param offset Index of the first buffer to read to.
184    * @param length The number of buffers to read to.
185    * @return Number of bytes read.
186    * @throws IOException If an error occurs or the dsts are not direct buffers.
187    */
readScattering(ByteBuffer[] dsts, int offset, int length)188   public long readScattering(ByteBuffer[] dsts, int offset, int length)
189     throws IOException
190   {
191     if (offset + length > dsts.length)
192       throw new IndexOutOfBoundsException("offset + length > dsts.length");
193 
194     return readScattering(nfd.getNativeFD(), dsts, offset, length);
195   }
196 
readScattering(int fd, ByteBuffer[] dsts, int offset, int length)197   private static native long readScattering(int fd, ByteBuffer[] dsts,
198                                             int offset, int length)
199     throws IOException;
200 
201   /**
202    * Receive a datagram on this channel, returning the host address
203    * that sent the datagram.
204    *
205    * @param dst Where to store the datagram.
206    * @return The host address that sent the datagram.
207    * @throws IOException
208    */
receive(ByteBuffer dst)209   public SocketAddress receive(ByteBuffer dst) throws IOException
210   {
211     if (kind != Kind.SOCK_DGRAM)
212       throw new SocketException("not a datagram socket");
213     ByteBuffer hostPort = ByteBuffer.allocateDirect(18);
214     int hostlen = receive(nfd.getNativeFD(), dst, hostPort);
215     if (hostlen == 0)
216       return null;
217     if (hostlen == 4) // IPv4
218       {
219         byte[] addr = new byte[4];
220         hostPort.get(addr);
221         int port = hostPort.getShort() & 0xFFFF;
222         return new InetSocketAddress(Inet4Address.getByAddress(addr), port);
223       }
224     if (hostlen == 16) // IPv6
225       {
226         byte[] addr = new byte[16];
227         hostPort.get(addr);
228         int port = hostPort.getShort() & 0xFFFF;
229         return new InetSocketAddress(Inet6Address.getByAddress(addr), port);
230       }
231 
232     throw new SocketException("host address received with invalid length: "
233                               + hostlen);
234   }
235 
receive(int fd, ByteBuffer dst, ByteBuffer address)236   private static native int receive (int fd, ByteBuffer dst, ByteBuffer address)
237     throws IOException;
238 
239   /**
240    * Writes from a direct byte bufer using the supplied file descriptor.
241    * Assumes the buffer is a DirectBuffer.
242    *
243    * @param src The source buffer.
244    * @return Number of bytes written.
245    * @throws IOException
246    */
write(ByteBuffer src)247   public int write(ByteBuffer src) throws IOException
248   {
249     return write(nfd.getNativeFD(), src);
250   }
251 
write(int fd, ByteBuffer src)252   private native int write(int fd, ByteBuffer src) throws IOException;
253 
254   /**
255    * Writes from byte buffers directly using the supplied file descriptor.
256    * Assumes the that buffer list constains DirectBuffers.  Will perform
257    * as gathering write.
258    *
259    * @param srcs
260    * @param offset
261    * @param length
262    * @return Number of bytes written.
263    * @throws IOException
264    */
writeGathering(ByteBuffer[] srcs, int offset, int length)265   public long writeGathering(ByteBuffer[] srcs, int offset, int length)
266     throws IOException
267   {
268     if (offset + length > srcs.length)
269       throw new IndexOutOfBoundsException("offset + length > srcs.length");
270 
271     // A gathering write is limited to 16 buffers; when writing, ensure
272     // that we have at least one buffer with something in it in the 16
273     // buffer window starting at offset.
274     while (!srcs[offset].hasRemaining() && offset < srcs.length)
275       offset++;
276 
277     // There are no buffers with anything to write.
278     if (offset == srcs.length)
279       return 0;
280 
281     // If we advanced `offset' so far that we don't have `length'
282     // buffers left, reset length to only the remaining buffers.
283     if (length > srcs.length - offset)
284       length = srcs.length - offset;
285 
286     return writeGathering(nfd.getNativeFD(), srcs, offset, length);
287   }
288 
writeGathering(int fd, ByteBuffer[] srcs, int offset, int length)289   private native long writeGathering(int fd, ByteBuffer[] srcs,
290                                      int offset, int length)
291     throws IOException;
292 
293   /**
294    * Send a datagram to the given address.
295    *
296    * @param src The source buffer.
297    * @param dst The destination address.
298    * @return The number of bytes written.
299    * @throws IOException
300    */
send(ByteBuffer src, InetSocketAddress dst)301   public int send(ByteBuffer src, InetSocketAddress dst)
302     throws IOException
303   {
304     InetAddress addr = dst.getAddress();
305     if (addr == null)
306       throw new NullPointerException();
307     if (addr instanceof Inet4Address)
308       return send(nfd.getNativeFD(), src, addr.getAddress(), dst.getPort());
309     else if (addr instanceof Inet6Address)
310       return send6(nfd.getNativeFD(), src, addr.getAddress(), dst.getPort());
311     else
312       throw new SocketException("unrecognized inet address type");
313   }
314 
315   // Send to an IPv4 address.
send(int fd, ByteBuffer src, byte[] addr, int port)316   private static native int send(int fd, ByteBuffer src, byte[] addr, int port)
317     throws IOException;
318 
319   // Send to an IPv6 address.
send6(int fd, ByteBuffer src, byte[] addr, int port)320   private static native int send6(int fd, ByteBuffer src, byte[] addr, int port)
321     throws IOException;
322 
323   /**
324    * Write a single byte.
325    *
326    * @param b The byte to write.
327    * @throws IOException
328    */
write(int b)329   public void write(int b) throws IOException
330   {
331     write(nfd.getNativeFD(), b);
332   }
333 
write(int fd, int b)334   private static native void write(int fd, int b) throws IOException;
335 
initIDs()336   private native static void initIDs();
337 
338   // Network (socket) specific methods.
339 
340   /**
341    * Create a new socket. This method will initialize the native file
342    * descriptor state of this instance.
343    *
344    * @param stream Whether or not to create a streaming socket, or a datagram
345    *  socket.
346    * @throws IOException If creating a new socket fails, or if this
347    *  channel already has its native descriptor initialized.
348    */
initSocket(boolean stream)349   public void initSocket(boolean stream) throws IOException
350   {
351     if (nfd.isValid())
352       throw new IOException("native FD already initialized");
353     if (stream)
354       kind = Kind.SOCK_STREAM;
355     else
356       kind = Kind.SOCK_DGRAM;
357     nfd.setNativeFD(socket(stream));
358   }
359 
360   /**
361    * Create a new socket, returning the native file descriptor.
362    *
363    * @param stream Set to true for streaming sockets; false for datagrams.
364    * @return The native file descriptor.
365    * @throws IOException If creating the socket fails.
366    */
socket(boolean stream)367   private static native int socket(boolean stream) throws IOException;
368 
369   /**
370    * Connect the underlying socket file descriptor to the remote host.
371    *
372    * @param saddr The address to connect to.
373    * @param timeout The connect timeout to use for blocking connects.
374    * @return True if the connection succeeded; false if the file descriptor
375    *  is in non-blocking mode and the connection did not immediately
376    *  succeed.
377    * @throws IOException If an error occurs while connecting.
378    */
connect(InetSocketAddress saddr, int timeout)379   public boolean connect(InetSocketAddress saddr, int timeout)
380     throws SocketException
381   {
382     int fd;
383 
384     InetAddress addr = saddr.getAddress();
385 
386     // Translates an IOException into a SocketException to conform
387     // to the throws clause.
388     try
389       {
390         fd = nfd.getNativeFD();
391       }
392     catch (IOException ioe)
393       {
394         throw new SocketException(ioe.getMessage());
395       }
396 
397     if (addr instanceof Inet4Address)
398       return connect(fd, addr.getAddress(), saddr.getPort(),
399                      timeout);
400     if (addr instanceof Inet6Address)
401       return connect6(fd, addr.getAddress(), saddr.getPort(),
402                       timeout);
403     throw new SocketException("unsupported internet address");
404   }
405 
connect(int fd, byte[] addr, int port, int timeout)406   private static native boolean connect(int fd, byte[] addr, int port, int timeout)
407     throws SocketException;
408 
connect6(int fd, byte[] addr, int port, int timeout)409   private static native boolean connect6(int fd, byte[] addr, int port, int timeout)
410     throws SocketException;
411 
412   /**
413    * Disconnect this channel, if it is a datagram socket. Disconnecting
414    * a datagram channel will disassociate it from any address, so the
415    * socket will remain open, but can send and receive datagrams from
416    * any address.
417    *
418    * @throws IOException If disconnecting this channel fails, or if this
419    *  channel is not a datagram channel.
420    */
disconnect()421   public void disconnect() throws IOException
422   {
423     if (kind != Kind.SOCK_DGRAM)
424       throw new IOException("can only disconnect datagram channels");
425     disconnect(nfd.getNativeFD());
426   }
427 
disconnect(int fd)428   private static native void disconnect(int fd) throws IOException;
429 
getLocalAddress()430   public InetSocketAddress getLocalAddress() throws IOException
431   {
432     if (!nfd.isValid())
433       return null;
434     ByteBuffer name = ByteBuffer.allocateDirect(18);
435     int namelen = getsockname(nfd.getNativeFD(), name);
436     if (namelen == 0) // not bound
437       return null; // XXX return some wildcard?
438     if (namelen == 4)
439       {
440         byte[] addr = new byte[4];
441         name.get(addr);
442         int port = name.getShort() & 0xFFFF;
443         return new InetSocketAddress(Inet4Address.getByAddress(addr), port);
444       }
445     if (namelen == 16)
446       {
447         byte[] addr = new byte[16];
448         name.get(addr);
449         int port = name.getShort() & 0xFFFF;
450         return new InetSocketAddress(Inet6Address.getByAddress(addr), port);
451       }
452     throw new SocketException("invalid address length");
453   }
454 
getsockname(int fd, ByteBuffer name)455   private static native int getsockname(int fd, ByteBuffer name)
456     throws IOException;
457 
458   /**
459    * Returns the socket address of the remote peer this channel is connected
460    * to, or null if this channel is not yet connected.
461    *
462    * @return The peer address.
463    * @throws IOException
464    */
getPeerAddress()465   public InetSocketAddress getPeerAddress() throws IOException
466   {
467     if (!nfd.isValid())
468       return null;
469     ByteBuffer name = ByteBuffer.allocateDirect(18);
470     int namelen = getpeername (nfd.getNativeFD(), name);
471     if (namelen == 0) // not connected yet
472       return null;
473     if (namelen == 4) // IPv4
474       {
475         byte[] addr = new byte[4];
476         name.get(addr);
477         int port = name.getShort() & 0xFFFF;
478         return new InetSocketAddress(Inet4Address.getByAddress(addr), port);
479       }
480     else if (namelen == 16) // IPv6
481       {
482         byte[] addr = new byte[16];
483         name.get(addr);
484         int port = name.getShort() & 0xFFFF;
485         return new InetSocketAddress(Inet6Address.getByAddress(addr), port);
486       }
487     throw new SocketException("invalid address length");
488   }
489 
490   /*
491    * The format here is the peer address, followed by the port number.
492    * The returned value is the length of the peer address; thus, there
493    * will be LEN + 2 valid bytes put into NAME.
494    */
getpeername(int fd, ByteBuffer name)495   private static native int getpeername(int fd, ByteBuffer name)
496     throws IOException;
497 
498   /**
499    * Accept an incoming connection, returning a new VMChannel, or null
500    * if the channel is nonblocking and no connection is pending.
501    *
502    * @return The accepted connection, or null.
503    * @throws IOException If an IO error occurs.
504    */
accept()505   public VMChannel accept() throws IOException
506   {
507     int new_fd = accept(nfd.getNativeFD());
508     if (new_fd == -1) // non-blocking accept had no pending connection
509       return null;
510     return new VMChannel(new_fd);
511   }
512 
accept(int native_fd)513   private static native int accept(int native_fd) throws IOException;
514 
515   // File-specific methods.
516 
517   /**
518    * Open a file at PATH, initializing the native state to operate on
519    * that open file.
520    *
521    * @param path The absolute file path.
522    * @throws IOException If the file cannot be opened, or if this
523    *  channel was previously initialized.
524    */
openFile(String path, int mode)525   public void openFile(String path, int mode) throws IOException
526   {
527     if (nfd.isValid() || nfd.isClosed())
528       throw new IOException("can't reinitialize this channel");
529     int fd = open(path, mode);
530     nfd.setNativeFD(fd);
531     kind = Kind.FILE;
532   }
533 
open(String path, int mode)534   private static native int open(String path, int mode) throws IOException;
535 
position()536   public long position() throws IOException
537   {
538     if (kind != Kind.FILE)
539       throw new IOException("not a file");
540     return position(nfd.getNativeFD());
541   }
542 
position(int fd)543   private static native long position(int fd) throws IOException;
544 
seek(long pos)545   public void seek(long pos) throws IOException
546   {
547     if (kind != Kind.FILE)
548       throw new IOException("not a file");
549     seek(nfd.getNativeFD(), pos);
550   }
551 
seek(int fd, long pos)552   private static native void seek(int fd, long pos) throws IOException;
553 
truncate(long length)554   public void truncate(long length) throws IOException
555   {
556     if (kind != Kind.FILE)
557       throw new IOException("not a file");
558     truncate(nfd.getNativeFD(), length);
559   }
560 
truncate(int fd, long len)561   private static native void truncate(int fd, long len) throws IOException;
562 
lock(long pos, long len, boolean shared, boolean wait)563   public boolean lock(long pos, long len, boolean shared, boolean wait)
564     throws IOException
565   {
566     if (kind != Kind.FILE)
567       throw new IOException("not a file");
568     return lock(nfd.getNativeFD(), pos, len, shared, wait);
569   }
570 
lock(int fd, long pos, long len, boolean shared, boolean wait)571   private static native boolean lock(int fd, long pos, long len,
572                                      boolean shared, boolean wait)
573     throws IOException;
574 
unlock(long pos, long len)575   public void unlock(long pos, long len) throws IOException
576   {
577     if (kind != Kind.FILE)
578       throw new IOException("not a file");
579     unlock(nfd.getNativeFD(), pos, len);
580   }
581 
unlock(int fd, long pos, long len)582   private static native void unlock(int fd, long pos, long len) throws IOException;
583 
size()584   public long size() throws IOException
585   {
586     if (kind != Kind.FILE)
587       throw new IOException("not a file");
588     return size(nfd.getNativeFD());
589   }
590 
size(int fd)591   private static native long size(int fd) throws IOException;
592 
map(char mode, long position, int size)593   public MappedByteBuffer map(char mode, long position, int size)
594     throws IOException
595   {
596     if (kind != Kind.FILE)
597       throw new IOException("not a file");
598     return map(nfd.getNativeFD(), mode, position, size);
599   }
600 
map(int fd, char mode, long position, int size)601   private static native MappedByteBuffer map(int fd, char mode,
602                                              long position, int size)
603     throws IOException;
604 
flush(boolean metadata)605   public boolean flush(boolean metadata) throws IOException
606   {
607     if (kind != Kind.FILE)
608       throw new IOException("not a file");
609     return flush(nfd.getNativeFD(), metadata);
610   }
611 
flush(int fd, boolean metadata)612   private static native boolean flush(int fd, boolean metadata) throws IOException;
613 
614   // Close.
615 
616   /**
617    * Close this socket. The socket is also automatically closed when this
618    * object is finalized.
619    *
620    * @throws IOException If closing the socket fails, or if this object has
621    *  no open socket.
622    */
close()623   public void close() throws IOException
624   {
625     nfd.close();
626   }
627 
close(int native_fd)628   static native void close(int native_fd) throws IOException;
629 
630   /**
631    * <p>Provides a simple mean for the JNI code to find out whether the
632    * current thread was interrupted by a call to Thread.interrupt().</p>
633    *
634    * @return
635    */
isThreadInterrupted()636   static boolean isThreadInterrupted()
637   {
638     return Thread.currentThread().isInterrupted();
639   }
640 
641   // Inner classes.
642 
643   /**
644    * A wrapper for a native file descriptor integer. This tracks the state
645    * of an open file descriptor, and ensures that
646    *
647    * This class need not be fully supported by virtual machines; if a
648    * virtual machine does not use integer file descriptors, or does and
649    * wishes to hide that, then the methods of this class may be stubbed out.
650    *
651    * System-specific classes that depend on access to native file descriptor
652    * integers SHOULD declare this fact.
653    */
654   public final class State
655   {
656     private int native_fd;
657     private boolean valid;
658     private boolean closed;
659 
State()660     State()
661     {
662       native_fd = -1;
663       valid = false;
664       closed = false;
665     }
666 
isValid()667     public boolean isValid()
668     {
669       return valid;
670     }
671 
isClosed()672     public boolean isClosed()
673     {
674       return closed;
675     }
676 
getNativeFD()677     public int getNativeFD() throws IOException
678     {
679       if (!valid)
680         throw new IOException("invalid file descriptor");
681       return native_fd;
682     }
683 
setNativeFD(final int native_fd)684     void setNativeFD(final int native_fd) throws IOException
685     {
686       if (valid)
687         throw new IOException("file descriptor already initialized");
688       this.native_fd = native_fd;
689       valid = true;
690     }
691 
close()692     public void close() throws IOException
693     {
694       if (!valid)
695         throw new IOException("invalid file descriptor");
696       try
697         {
698           VMChannel.close(native_fd);
699         }
700       finally
701         {
702           valid = false;
703           closed = true;
704         }
705     }
706 
toString()707     public String toString()
708     {
709       if (closed)
710         return "<<closed>>";
711       if (!valid)
712         return "<<invalid>>";
713       return String.valueOf(native_fd);
714     }
715 
finalize()716     protected void finalize() throws Throwable
717     {
718       try
719         {
720           if (valid)
721             close();
722         }
723       finally
724         {
725           super.finalize();
726         }
727     }
728   }
729 
730   /**
731    * An enumeration of possible kinds of channel.
732    */
733   static class Kind // XXX enum
734   {
735     /** A streaming (TCP) socket. */
736     static final Kind SOCK_STREAM = new Kind();
737 
738     /** A datagram (UDP) socket. */
739     static final Kind SOCK_DGRAM = new Kind();
740 
741     /** A file. */
742     static final Kind FILE = new Kind();
743 
744     /** Something else; not a socket or file. */
745     static final Kind OTHER = new Kind();
746 
Kind()747     private Kind() { }
748   }
749 }
750