/* gnu_java_nio_VMChannel.c -
   Copyright (C) 2003, 2004, 2005, 2006, 2007  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. */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <config-int.h>

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/uio.h>

#include <netinet/in.h>

#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

#include <jni.h>
#include <jcl.h>

#include "cpio.h"
#include "gnu_java_nio_VMChannel.h"
#include "javanio.h"

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif /* HAVE_FCNTL_H */

#if defined(HAVE_SYS_IOCTL_H)
#define BSD_COMP /* Get FIONREAD on Solaris2 */
#include <sys/ioctl.h>
#endif
#if defined(HAVE_SYS_FILIO_H) /* Get FIONREAD on Solaris 2.5 */
#include <sys/filio.h>
#endif

#define CONNECT_EXCEPTION "java/net/ConnectException"
#define IO_EXCEPTION "java/io/IOException"
#define SOCKET_EXCEPTION "java/net/SocketException"
#define INTERRUPTED_IO_EXCEPTION "java/io/InterruptedIOException"
#define NON_READABLE_CHANNEL_EXCEPTION "java/nio/channels/NonReadableChannelException"
#define NON_WRITABLE_CHANNEL_EXCEPTION "java/nio/channels/NonWritableChannelException"
#define SOCKET_TIMEOUT_EXCEPTION "java/net/SocketTimeoutException"

/* Align a value up or down to a multiple of the pagesize. */
#define ALIGN_DOWN(p,s) ((p) - ((p) % (s)))
#define ALIGN_UP(p,s) ((p) + ((s) - ((p) % (s))))

/*
 * Limit to maximum of 16 buffers
 */
#define JCL_IOV_MAX 16

#ifdef __cplusplus
extern "C"
{
#endif

enum JCL_buffer_type { DIRECT, HEAP, ARRAY, UNKNOWN };

struct JCL_buffer
{
  enum JCL_buffer_type type;
  jbyte *ptr;
  jint offset;
  jint position;
  jint limit;
  jint count;
};

jmethodID get_method_id(JNIEnv *, jclass, const char *, const char *);
void JCL_print_buffer(JNIEnv *, struct JCL_buffer *);
int JCL_init_buffer(JNIEnv *, struct JCL_buffer *, jobject);
void JCL_release_buffer(JNIEnv *, struct JCL_buffer *, jobject, jint);
void JCL_cleanup_buffers(JNIEnv *, struct JCL_buffer *, jint, jobjectArray, jint, jlong);
int JCL_thread_interrupted(JNIEnv *);

static jfieldID address_fid;
static jmethodID get_position_mid;
static jmethodID set_position_mid;
static jmethodID get_limit_mid;
static jmethodID set_limit_mid;
static jmethodID has_array_mid;
static jmethodID array_mid;
static jmethodID array_offset_mid;
static jmethodID thread_interrupted_mid;
static jclass vm_channel_class;

jmethodID
get_method_id(JNIEnv *env,  jclass clazz, const char *name, 
	          const char *sig)
{
  jmethodID mid = (*env)->GetMethodID(env, clazz, name, sig);
/*   NIODBG("name: %s; sig: %s", name, sig); */
  if (mid == NULL)
    {
      JCL_ThrowException(env, "java/lang/InternalError", name);
      return NULL;
    }
  
  return mid;
}

inline void
JCL_print_buffer(JNIEnv *env __attribute__((__unused__)), struct JCL_buffer *buf)
{
  fprintf (stderr, "Buffer - type: %d, ptr: %p\n", buf->type, buf->ptr);
}


int
JCL_init_buffer(JNIEnv *env, struct JCL_buffer *buf, jobject bbuf)
{
  void *addr = (*env)->GetDirectBufferAddress (env, bbuf);

/*   NIODBG("buf: %p; bbuf: %p; addr: %p", (void *) buf, bbuf, addr); */
  
  buf->position = (*env)->CallIntMethod(env, bbuf, get_position_mid);
  buf->limit = (*env)->CallIntMethod(env, bbuf, get_limit_mid);
  buf->offset = 0;
  buf->count = 0;
  buf->type = UNKNOWN;
    
  if (addr != NULL)
    {
      buf->ptr = (jbyte *) addr;
      buf->type = DIRECT;
    }
  else
    {
      jboolean has_array;
      has_array = (*env)->CallBooleanMethod(env, bbuf, has_array_mid);
      
      if (has_array == JNI_TRUE)
        {
          jbyteArray arr;
          buf->offset = (*env)->CallIntMethod(env, bbuf, array_offset_mid);
          arr = (*env)->CallObjectMethod(env, bbuf, array_mid);
          buf->ptr = (*env)->GetByteArrayElements(env, arr, 0);
          buf->type = ARRAY;
          (*env)->DeleteLocalRef(env, arr);
        }
      else
        {
          jobject address = (*env)->GetObjectField (env, bbuf, address_fid);
          if (address == NULL)
            return -1; /* XXX handle non-array, non-native buffers? */
          buf->ptr = (jbyte *) JCL_GetRawData(env, address);
          buf->type = HEAP;
          (*env)->DeleteLocalRef(env, address);
        }
    }
      
  return 0;
}

void
JCL_release_buffer(JNIEnv *env, struct JCL_buffer *buf, jobject bbuf, 
    jint action)
{
  jbyteArray arr;

/*   NIODBG("buf: %p; bbuf: %p; action: %x", (void *) buf, bbuf, action); */
  
  /* Set the position to the appropriate value */
  if (buf->count > 0)
    {
      jobject bbufTemp;
      bbufTemp = (*env)->CallObjectMethod(env, bbuf, set_position_mid, 
                                          buf->position + buf->count);
      (*env)->DeleteLocalRef(env, bbufTemp);
    }
    
  switch (buf->type)
    {
    case DIRECT:
    case HEAP:
      break;
    case ARRAY:
      arr = (*env)->CallObjectMethod(env, bbuf, array_mid);
      (*env)->ReleaseByteArrayElements(env, arr, buf->ptr, action);
      (*env)->DeleteLocalRef(env, arr);
      break;
    case UNKNOWN:
      /* TODO: Handle buffers that are not direct or array backed */
      break;
    }
}

void
JCL_cleanup_buffers(JNIEnv *env, 
                    struct JCL_buffer *bi_list, 
                    jint vec_len, 
                    jobjectArray bbufs, 
                    jint offset,
                    jlong num_bytes)
{
  jint i;

/*   NIODBG("bi_list: %p; vec_len: %d; bbufs: %p; offset: %d; num_bytes: %lld", */
/*       (void *) bi_list, vec_len, bbufs, offset, num_bytes); */
  
  /* Update all of the bbufs with the approriate information */
  for (i = 0; i < vec_len; i++)
    {
      struct JCL_buffer* buf;
      jobject bbuf;
      
      buf = &bi_list[i];
      bbuf = (*env)->GetObjectArrayElement(env, bbufs, offset + i);

      if (num_bytes > (buf->limit - buf->position))
        buf->count = (buf->limit - buf->position);
      else
        buf->count = num_bytes;
        
      num_bytes -= buf->count;
      
      JCL_release_buffer(env, buf, bbuf, JNI_ABORT);
      (*env)->DeleteLocalRef(env, bbuf);
    }
}


int
JCL_thread_interrupted(JNIEnv *env)
{
  return (int) (*env)->CallStaticBooleanMethod(env, vm_channel_class,
					       thread_interrupted_mid);
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    stdin_fd
 * Signature: ()I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_stdin_1fd (JNIEnv *env __attribute__((unused)),
                                       jclass c __attribute__((unused)))
{
/*   NIODBG("%d", fileno (stdin)); */
  return fileno (stdin);
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    stdout_fd
 * Signature: ()I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_stdout_1fd (JNIEnv *env __attribute__((unused)),
                                       jclass c __attribute__((unused)))
{
/*   NIODBG("%d", fileno (stdout)); */
  return fileno (stdout);
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    stderr_fd
 * Signature: ()I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_stderr_1fd (JNIEnv *env __attribute__((unused)),
                                       jclass c __attribute__((unused)))
{
/*   NIODBG("%d", fileno (stderr)); */
  return fileno (stderr);
}


JNIEXPORT void JNICALL 
Java_gnu_java_nio_VMChannel_initIDs  (JNIEnv *env, 
	jclass clazz)
{
  jclass bufferClass = JCL_FindClass(env, "java/nio/Buffer");
  jclass byteBufferClass = JCL_FindClass(env, "java/nio/ByteBuffer");

/*   NIODBG("%s", "..."); */

  address_fid = (*env)->GetFieldID(env, bufferClass, "address", 
                                   "Lgnu/classpath/Pointer;");
  if (address_fid == NULL)
    {
  	  JCL_ThrowException(env, "java/lang/InternalError", 
  	  	"Unable to find internal field");
      return;
    }
  
  get_position_mid = get_method_id(env, bufferClass, "position", "()I");
  set_position_mid = get_method_id(env, bufferClass, "position", 
                                   "(I)Ljava/nio/Buffer;");
  get_limit_mid = get_method_id(env, bufferClass, "limit", "()I");
  set_limit_mid = get_method_id(env, bufferClass, "limit", 
                                "(I)Ljava/nio/Buffer;");
  has_array_mid = get_method_id(env, byteBufferClass, "hasArray", "()Z");
  array_mid = get_method_id(env, byteBufferClass, "array", "()[B");
  array_offset_mid = get_method_id(env, byteBufferClass, "arrayOffset", "()I");
  
  vm_channel_class = clazz;
  thread_interrupted_mid = (*env)->GetStaticMethodID(env, clazz,
                                                  "isThreadInterrupted",
                                                  "()Z");
}

JNIEXPORT void JNICALL 
Java_gnu_java_nio_VMChannel_setBlocking (JNIEnv *env, 
	jobject o __attribute__ ((__unused__)), 
	jint fd, 
	jboolean blocking)
{
  int opts;
  
/*   NIODBG("fd: %d; blocking: %d", fd, blocking); */

  opts = fcntl(fd, F_GETFL);
  if (opts < 0)
    {
      JCL_ThrowException(env, IO_EXCEPTION, 
        "Failed to get flags for file desriptor");
      return;
    }
  
  if (blocking == JNI_TRUE)
    opts &= ~(O_NONBLOCK);
  else
    opts |= O_NONBLOCK;
  
  opts = fcntl(fd, F_SETFL, opts);
  
  if (opts < 0)
    {
      JCL_ThrowException(env, IO_EXCEPTION, 
        "Failed to set flags for file desriptor");
      return;
    }  
}

/* Return true if fd is in non-blocking mode. */
static jboolean
is_non_blocking_fd(jint fd)
{
  int opts;
  opts = fcntl(fd, F_GETFL);
  if (opts == -1)
    {
      /* Assume blocking on error. */
      return 0;
    }
  return (opts & O_NONBLOCK) != 0;
}

JNIEXPORT jint JNICALL 
Java_gnu_java_nio_VMChannel_read__ILjava_nio_ByteBuffer_2 (JNIEnv *env,
                                                           jobject o __attribute__ ((__unused__)), 
                                                           jint fd, 
                                                           jobject bbuf)
{
#ifdef HAVE_READ
  jint len;
  ssize_t result;
  struct JCL_buffer buf;
  int tmp_errno;

/*   NIODBG("fd: %d; bbuf: %p", fd, bbuf); */
  
  if (JCL_init_buffer(env, &buf, bbuf) < 0)
    {
      /* TODO: Rethrown exception */
      JCL_ThrowException (env, IO_EXCEPTION, "Buffer initialisation failed");
      return -1;
    }

  len = buf.limit - buf.position;

  if (len == 0)
    {
      JCL_release_buffer (env, &buf, bbuf, JNI_ABORT);
      return 0;
    }
  
  do 
    {
      result = cpnio_read (fd, &(buf.ptr[buf.position + buf.offset]), len);
      tmp_errno = errno;
    }
  while (result == -1 && errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmp_errno;
  
  if (result == 0)
    {
      result = -1;
      buf.count = 0;
    }
  else if (result == -1)
    {
      buf.count = 0;
      if (errno == EAGAIN)
        {
          if (is_non_blocking_fd(fd))
            {
              /* Non-blocking */
              result = 0;
            }
          else
            {
              /* Read timeout on a socket with SO_RCVTIMEO != 0. */
              JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
              JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "read timed out");
              return -1;
            }
        }
      else if (errno == EBADF) /* Bad fd */
        {
          JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
          JCL_ThrowException (env, NON_READABLE_CHANNEL_EXCEPTION, 
                              strerror(errno));
          return -1;
        }
      else if (EINTR == errno) /* read interrupted */
        {
          JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
          JCL_ThrowException(env, INTERRUPTED_IO_EXCEPTION, strerror (errno));
          return -1;
        }
      else
        {
          JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
      	  JCL_ThrowException (env, IO_EXCEPTION, strerror(errno));
      	  return -1;
        }
    }
  else 
    buf.count = result;
      
  JCL_release_buffer(env, &buf, bbuf, 0);
  
  return result;
#else
  (void) fd;
  (void) bbuf;
  JCL_ThrowException (env, IO_EXCEPTION, "read not supported");
  return -1;
#endif /* HAVE_READ */
}

JNIEXPORT jint JNICALL 
Java_gnu_java_nio_VMChannel_write__ILjava_nio_ByteBuffer_2 (JNIEnv *env, 
                                                            jobject o __attribute__ ((__unused__)), 
                                                            jint fd, 
                                                            jobject bbuf)
{
#ifdef HAVE_WRITE
  jint len;
  ssize_t result;
  struct JCL_buffer buf;
  int tmp_errno;

/*   NIODBG("fd: %d; bbuf: %p", fd, bbuf); */
  
  if (JCL_init_buffer(env, &buf, bbuf) < 0)
    {
      /* TODO: Rethrown exception */
      JCL_ThrowException (env, IO_EXCEPTION, "Buffer initialisation failed");
      return -1;
    }

  len = buf.limit - buf.position;

  if (len == 0)
    {
      JCL_release_buffer (env, &buf, bbuf, JNI_ABORT);
      return 0;
    }
  
  do
    {
      result = cpnio_write (fd, &(buf.ptr[buf.position + buf.offset]), len);
      tmp_errno = errno;
    }
  while (result == -1 && errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmp_errno;

  buf.count = result;

  if (result == -1)
    {
      if (errno == EAGAIN) /* Non-blocking */
        {
          result = 0;
        }
      else
        {
          JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
          JCL_ThrowException(env, IO_EXCEPTION, strerror(errno));
          return -1;
        }
    }
    
  JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
  
  return result;
#else
  (void) fd;
  (void) bbuf;
  JCL_ThrowException (env, IO_EXCEPTION, "write not supported");
  return -1;
#endif /* HAVE_WRITE */
}


/*
 * Implementation of a scattering read.  Will use the appropriate
 * vector based read call (currently readv on Linux).
 * 
 * This has a limit to the number of buffers that will be read.  It
 * will not make muliple readv calls.  This is to ensure that operations 
 * are atomic.  Currently it is limited to 16 buffers.  This is for 
 * compatibiliy with Sun.
 */
JNIEXPORT jlong JNICALL 
Java_gnu_java_nio_VMChannel_readScattering (JNIEnv *env, 
	jobject o __attribute__ ((__unused__)), 
	jint fd, 
	jobjectArray bbufs, 
	jint offset, 
	jint length)
{
  jint i;
/*   jboolean is_error = JNI_FALSE; */
/*   char *error_msg; */
  struct iovec buffers[JCL_IOV_MAX];
  struct JCL_buffer bi_list[JCL_IOV_MAX];
  ssize_t result;
  jint vec_len = length < JCL_IOV_MAX ? length : JCL_IOV_MAX;
  jlong bytes_read = 0;
  int tmp_errno;

/*   NIODBG("fd: %d; bbufs: %p; offset: %d; length: %d", */
/*          fd, bbufs, offset, length); */
  
  /* Build the vector of buffers to read into */
  for (i = 0; i < vec_len; i++)
    {
      struct JCL_buffer* buf;
      jobject bbuf;
      
      buf = &bi_list[i];
      bbuf = (*env)->GetObjectArrayElement(env, bbufs, offset + i);
      
      JCL_init_buffer(env, buf, bbuf);

/*       JCL_print_buffer (env, buf); */
      
      buffers[i].iov_base = &(buf->ptr[buf->position + buf->offset]);
      buffers[i].iov_len = buf->limit - buf->position;
      (*env)->DeleteLocalRef(env, bbuf);
    }
    
  /* Work the scattering magic */
  do
    {
      result = cpnio_readv (fd, buffers, vec_len);
      tmp_errno = errno;
    }
  while (result == -1 && errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmp_errno;
  bytes_read = (jlong) result;
  
  /* Handle the response */
  if (result < 0)
    {
      if (errno == EAGAIN)
        {
          if (is_non_blocking_fd(fd))
            {
              /* Non-blocking */
              result = 0;
            }
          else
            {
              /* Read timeout on a socket with SO_RCVTIMEO != 0. */
              JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_read);
              JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "read timed out");
              return -1;
            }
        }
      else if (errno == EBADF) /* Bad fd */
        {
          JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_read);
          JCL_ThrowException (env, NON_READABLE_CHANNEL_EXCEPTION, 
                              strerror(errno));
          return -1;
        } 
      else
        {
          JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_read);
          JCL_ThrowException (env, IO_EXCEPTION, strerror(errno));
          return -1;
        }
      bytes_read = 0;
    }
  else if (result == 0) /* EOF */
    {
      result = -1;
    }
    
  JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_read);
                  
  return (jlong) result;
}


/*
 * Implementation of a gathering write.  Will use the appropriate
 * vector based read call (currently readv on Linux).
 * 
 * This has a limit to the number of buffers that will be read.  It
 * will not make muliple readv calls.  This is to ensure that operations 
 * are atomic.  Currently it is limited to 16 buffers.  This is for 
 * compatibiliy with Sun.
 */
JNIEXPORT jlong JNICALL 
Java_gnu_java_nio_VMChannel_writeGathering (JNIEnv *env, 
	jobject o __attribute__ ((__unused__)), 
	jint fd, 
	jobjectArray bbufs, 
	jint offset, 
	jint length)
{
  int i;
/*   jboolean is_error = JNI_FALSE; */
/*   char *error_msg; */
  struct iovec buffers[JCL_IOV_MAX];
  struct JCL_buffer bi_list[JCL_IOV_MAX];
  ssize_t result;
  jint vec_len = length < JCL_IOV_MAX ? length : JCL_IOV_MAX;
  jlong bytes_written;
  int tmp_errno;
  
/*   NIODBG("fd: %d; bbufs: %p; offset: %d; length: %d", */
/*          fd, bbufs, offset, length); */
  
  /* Build the vector of buffers to read into */
  for (i = 0; i < vec_len; i++)
    {
      struct JCL_buffer* buf;
      jobject bbuf;
      
      buf = &bi_list[i];
      bbuf = (*env)->GetObjectArrayElement(env, bbufs, offset + i);
      
      JCL_init_buffer(env, buf, bbuf); 
      
/*       JCL_print_buffer(env, buf); */

      buffers[i].iov_base = &(buf->ptr[buf->position + buf->offset]);
      buffers[i].iov_len = buf->limit - buf->position;
      (*env)->DeleteLocalRef(env, bbuf);
    }
    
  /* Work the gathering magic */
  do
    {
      result = cpnio_writev (fd, buffers, vec_len);
      tmp_errno = errno;
    }
  while (result == -1 && tmp_errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmp_errno;

  bytes_written = (jlong) result;

  if (result < 0)
    {
      bytes_written = 0;
      if (errno == EAGAIN) /* Non blocking */
        result = 0;
      else if (errno == EBADF) /* Bad fd */
        {
          JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, 
                              bytes_written);
          JCL_ThrowException (env, NON_WRITABLE_CHANNEL_EXCEPTION, 
                              strerror(errno));
          return -1;
        } 
      else
        {
          JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset,
                              bytes_written);
          JCL_ThrowException (env, IO_EXCEPTION, strerror(errno));
          return -1;
        }
    }
  else if (result == 0) /* EOF??  Does this happen on a write */
    result = -1;
    
  JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_written);    
  return (jlong) result;
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    receive
 * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_receive (JNIEnv *env,
                                     jclass c __attribute__((unused)),
                                     jint fd, jobject dst, jobject addrPort)
{
#ifdef HAVE_RECVFROM
  char *addrPortPtr = (*env)->GetDirectBufferAddress (env, addrPort);
  struct JCL_buffer buf;
#ifdef HAVE_INET6
  struct sockaddr_in6 sock_storage;
  struct sockaddr_in6 *sock6;
  socklen_t slen = sizeof (struct sockaddr_in6);
#else
  struct sockaddr_in sock_storage;
  socklen_t slen = sizeof (struct sockaddr_in);
#endif /* HAVE_INET6 */
  struct sockaddr *sockaddr = (struct sockaddr *) &sock_storage;
  struct sockaddr_in *sock4;
  int ret;
  jint result = -1;

  if (JCL_init_buffer (env, &buf, dst) == -1)
    JCL_ThrowException (env, IO_EXCEPTION, "loading buffer failed");

#ifndef HAVE_MSG_WAITALL
#define MSG_WAITALL       0
#endif

  ret = cpnio_recvfrom (fd, &(buf.ptr[buf.position + buf.offset]),
                        buf.limit - buf.position, MSG_WAITALL,
                        sockaddr, &slen);

  if (-1 == ret)
    {
      JCL_release_buffer (env, &buf, dst, JNI_ABORT);
      if (EINTR == errno)
        JCL_ThrowException (env, "java/io/InterruptedIOException", strerror (errno));
      else if (EAGAIN == errno)
        {
          /* If the socket is in blocking mode, our timeout expired. */
          int val = fcntl (fd, F_GETFL, 0);
          if (val == -1)
            JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
          else if ((val & O_NONBLOCK) == 0)
            JCL_ThrowException (env, "java/net/SocketTimeoutException",
                                "read timed out");
        }
      else
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return 0;
    }

  if (sockaddr->sa_family == AF_INET)
    {
      sock4 = (struct sockaddr_in *) sockaddr;
      memcpy (addrPortPtr, &(sock4->sin_addr.s_addr), 4);
      ;memcpy (addrPortPtr + 4, &(sock4->sin_port), 2);
      result = 4;
    }
#ifdef HAVE_INET6
  else if (sockaddr->sa_family == AF_INET6)
    {
      sock6 = (struct sockaddr_in6 *) sockaddr;
      memcpy (addrPortPtr, &(sock6->sin6_addr.s6_addr), 16);
      memcpy (addrPortPtr + 16, &(sock6->sin6_port), 2);
      result = 16;
    }
#endif /* HAVE_INET6 */
  else if (ret == 0)
    {
      result = 0;
    }
  else
    {
      JCL_ThrowException (env, "java/net/SocketException",
                          "unsupported address type returned");
    }

  buf.count += ret;
  JCL_release_buffer (env, &buf, dst, 0);
  return result;
#else
  (void) fd;
  (void) dst;
  (void) addrPort;
  JCL_ThrowException (env, IO_EXCEPTION, "recvfrom not supported");
#endif /* HAVE_RECVFROM */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    send
 * Signature: (Ljava/nio/ByteBuffer;[BI)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_send (JNIEnv *env,
                                  jclass c __attribute__((unused)),
                                  int fd, jobject src, jbyteArray addr, jint port)
{
#ifdef HAVE_SENDTO
  struct sockaddr_in sockaddr;
  jbyte *elems;
  struct JCL_buffer buf;
  int ret;

/*   NIODBG("fd: %d; src: %p; addr: %p; port: %d", */
/*          fd, src, addr, port); */

  if (JCL_init_buffer (env, &buf, src) == -1)
    {
      JCL_ThrowException (env, IO_EXCEPTION, "loading buffer failed");
      return -1;
    }

/*   JCL_print_buffer (env, &buf); */

  elems = (*env)->GetByteArrayElements (env, addr, NULL);

  sockaddr.sin_family = AF_INET;
  sockaddr.sin_addr.s_addr = *((uint32_t *) elems);
  sockaddr.sin_port = htons (port);

  do
    {
      ret = cpnio_sendto (fd, &(buf.ptr[buf.position + buf.offset]),
                          buf.limit - buf.position,
                          0, (const struct sockaddr *) &sockaddr,
                          sizeof (struct sockaddr_in));
    }
  while (-1 == ret && EINTR == errno);

  (*env)->ReleaseByteArrayElements (env, addr, elems, JNI_ABORT);

  if (-1 == ret)
    {
      if (errno != EAGAIN)
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      JCL_release_buffer (env, &buf, src, JNI_ABORT);
      return 0;
    }

  buf.count += ret;
  JCL_release_buffer (env, &buf, src, JNI_ABORT);
  return ret;
#else
  (void) fd;
  (void) src;
  (void) addr;
  (void) port;
#endif /* HAVE_SENDTO */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    send6
 * Signature: (Ljava/nio/ByteBuffer;[BI)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_send6 (JNIEnv *env,
                                   jclass c __attribute__((unused)),
                                   int fd, jobject src, jbyteArray addr, jint port)
{
#if defined(HAVE_SENDTO) && defined(HAVE_INET6)
  struct sockaddr_in6 sockaddr;
  jbyte *elems;
  struct JCL_buffer buf;
  int ret;

/*   NIODBG("fd: %d; src: %p; addr: %p; port: %d", */
/*          fd, src, addr, port); */

  if (JCL_init_buffer (env, &buf, src) == -1)
    {
      JCL_ThrowException (env, IO_EXCEPTION, "loading buffer failed");
      return -1;
    }

/*   JCL_print_buffer (env, &buf); */

  elems = (*env)->GetByteArrayElements (env, addr, NULL);

  sockaddr.sin6_family = AF_INET6;
  memcpy (&sockaddr.sin6_addr.s6_addr, elems, 16);
  sockaddr.sin6_port = htons (port);

  do
    {
      ret = cpnio_sendto (fd, (const void *) (buf.ptr + buf.offset),
                          buf.limit - buf.position,
                          0, (const struct sockaddr *) &sockaddr,
                          sizeof (struct sockaddr_in6));
    }
  while (-1 == ret && EINTR == errno);

  (*env)->ReleaseByteArrayElements (env, addr, elems, JNI_ABORT);

  if (-1 == ret)
    {
      if (errno != EAGAIN)
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      JCL_release_buffer (env, &buf, src, JNI_ABORT);
      return 0;
    }

  buf.count += ret;
  JCL_release_buffer (env, &buf, src, JNI_ABORT);
  return ret;
#else
  (void) fd;
  (void) src;
  (void) addr;
  (void) port;
  JCL_ThrowException (env, IO_EXCEPTION, "IPv6 sendto not supported");
  return -1;
#endif /* HAVE_SENDTO && HAVE_INET6 */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    read
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_read__I (JNIEnv *env,
                                     jclass c __attribute__((unused)),
                                     jint fd)
{
#ifdef HAVE_READ
  char in;
  int ret;
  int tmp_errno;

/*   NIODBG("fd: %d", fd); */

  do
    {
      ret = cpnio_read (fd, &in, 1);
      tmp_errno = errno;
    }
  while (ret == -1 && errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmp_errno;

  if (-1 == ret)
    {
      if (errno == EAGAIN && !is_non_blocking_fd(fd))
        {
          /* Read timeout on a socket with SO_RCVTIMEO != 0. */
          JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "read timed out");
        }
      else
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return -1;
    }
  
  if (0 == ret)
    return -1;

  return (in & 0xFF);
#else
  (void) fd;
  JCL_ThrowException (env, IO_EXCEPTION, "read not supported");
#endif /* HAVE_READ */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    write
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_write__II (JNIEnv *env,
                                       jclass c __attribute__((unused)),
                                       jint fd, jint data)
{
#ifdef HAVE_WRITE
  char out = (char) data;
  int ret;
  int tmp_errno;

/*   NIODBG("fd: %d; data: %d", fd, data); */

  do
    {
      ret = cpnio_write (fd, &out, 1);
      tmp_errno = errno;
    }
  while (ret == -1 && errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmp_errno;

  if (-1 == ret)
    JCL_ThrowException(env, IO_EXCEPTION, strerror (errno));
#else
  (void) fd;
  (void) data;
  JCL_ThrowException (env, IO_EXCEPTION, "write not supported");
#endif /* HAVE_WRITE */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    socket
 * Signature: (Z)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_socket (JNIEnv *env, jclass clazz __attribute__((unused)),
                                    jboolean stream)
{
#ifdef HAVE_SOCKET
  int ret;

  do
    {
      ret = cpnio_socket (AF_INET, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
    }
  while (-1 == ret && EINTR == errno);

  if (ret == -1)
    JCL_ThrowException (env, "java/net/SocketException", strerror (errno));
/*   NIODBG("created socket %d", ret); */

  return ret;
#else
  (void) stream;
  JCL_ThrowException (env, IO_EXCEPTION, "socket not supported");
  return -1;
#endif /* HAVE_SOCKET */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    connect
 * Signature: (I[BI)Z
 */
JNIEXPORT jboolean JNICALL
Java_gnu_java_nio_VMChannel_connect (JNIEnv *env, jclass clazz __attribute__((unused)),
                                     jint fd, jbyteArray addr, jint port, jint timeout)
{
#ifdef HAVE_CONNECT
  struct sockaddr_in sockaddr;
  struct timeval timeo;
  int origflags = 0, flags;
  jbyte *addr_elems;
  int ret;
  int tmpErrno;

  if ((*env)->GetArrayLength (env, addr) != 4)
    {
      JCL_ThrowException (env, SOCKET_EXCEPTION,
                          "expecting 4-byte address");
      return JNI_FALSE;
    }

  if (timeout > 0)
    {
      timeo.tv_sec = timeout / 1000;
      timeo.tv_usec = (timeout % 1000) * 1000;
      origflags = fcntl (fd, F_GETFL, 0);
      if (origflags == -1)
        {
          JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
          return JNI_FALSE;
        }
      /* Set nonblocking mode, if not already set. */
      if (!(origflags & O_NONBLOCK))
        {
          flags = origflags | O_NONBLOCK;
          if (fcntl (fd, F_SETFL, flags) == -1)
            {
              JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
              return JNI_FALSE;
            }
        }
    }

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);

  memset (&sockaddr, 0, sizeof (struct sockaddr_in));
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_port = htons (port);
  sockaddr.sin_addr.s_addr = *((uint32_t *) addr_elems);


  do
    {
      ret = cpnio_connect (fd, (struct sockaddr *) &sockaddr,
                           sizeof (struct sockaddr_in));
      tmpErrno = errno;
    }
  while (ret == -1 && errno == EINTR && ! JCL_thread_interrupted(env));
  errno = tmpErrno;

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  /* If a timeout was specified, select on the file descriptor with
     the timeout. */
  if (timeout > 0 && ret == -1)
    {
      /* Reset the non-blocking flag, if needed. */
      if (!(origflags & O_NONBLOCK))
        {
          if (fcntl (fd, F_SETFL, origflags) == -1)
            {
              /* oops */
              JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
              return JNI_FALSE;
            }
        }
      if (EINPROGRESS == errno)
        {
          fd_set wrfds;
          FD_ZERO(&wrfds);
          FD_SET(fd, &wrfds);
          ret = cpnio_select (fd + 1, NULL, &wrfds, NULL, &timeo);
          if (ret == -1)
            {
              JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
              return JNI_FALSE;
            }
          if (ret == 0) /* connect timed out */
            {
              JCL_ThrowException (env, SOCKET_TIMEOUT_EXCEPTION,
                                  "connect timed out");
              return JNI_FALSE;
            }
          return JNI_TRUE; /* Connected! */
        }
      else if (ECONNREFUSED == errno)
        {
          JCL_ThrowException (env, CONNECT_EXCEPTION,
                              strerror (errno));
          return JNI_FALSE;
        }
      else
        {
          JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
          return JNI_FALSE;
        }
    }

  if (ret == -1)
    {
      if (EINPROGRESS == errno)
        return JNI_FALSE;
      else if (ECONNREFUSED == errno)
        {
          JCL_ThrowException (env, CONNECT_EXCEPTION,
                              strerror (errno));
          return JNI_FALSE;
        }
      else
        {
          JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
          return JNI_FALSE;
        }
    }

  return JNI_TRUE;
#else
  (void) fd;
  (void) addr;
  (void) port;
  (void) timeout;
  JCL_ThrowException (env, SOCKET_EXCEPTION, "connect not supported");
  return JNI_FALSE;
#endif /* HAVE_CONNECT */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    connect6
 * Signature: (I[BI)Z
 */
JNIEXPORT jboolean JNICALL
Java_gnu_java_nio_VMChannel_connect6 (JNIEnv *env, jclass clazz __attribute__((unused)),
                                      jint fd, jbyteArray addr, jint port, int timeout)
{
#if defined(HAVE_CONNECT) && defined(HAVE_INET6)
  struct sockaddr_in6 sockaddr;
  struct timeval timeo;
  int flags, origflags = 0;
  jbyte *addr_elems;
  int ret;

  if (timeout > 0)
    {
      timeo.tv_sec = timeout / 1000;
      timeo.tv_usec = (timeout % 1000) * 1000;
      origflags = fcntl (fd, F_GETFL, 0);
      if (origflags == -1)
        {
          JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
          return JNI_FALSE;
        }
      /* Set nonblocking mode, if not already set. */
      if (!(origflags & O_NONBLOCK))
        {
          flags = origflags | O_NONBLOCK;
          if (fcntl (fd, F_SETFL, flags) == -1)
            {
              JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
              return JNI_FALSE;
            }
        }
    }

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);

  memset (&sockaddr, 0, sizeof (struct sockaddr_in6));
  sockaddr.sin6_family = AF_INET6;
  sockaddr.sin6_port = htons (port);
  memcpy (&sockaddr.sin6_addr.s6_addr, addr_elems, 16);

  ret = cpnio_connect (fd, (struct sockaddr *) &sockaddr,
                       sizeof (struct sockaddr_in6));

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  /* If a timeout was specified, select on the file descriptor with
     the timeout. */
  if (timeout > 0 && ret == -1)
    {
      /* Reset the non-blocking flag, if needed. */
      if (!(origflags & O_NONBLOCK))
        {
          if (fcntl (fd, F_SETFL, origflags) == -1)
            {
              /* oops */
              JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
              return JNI_FALSE;
            }
        }
      if (EINPROGRESS == errno)
        {
          fd_set wrfds;
          FD_ZERO(&wrfds);
          FD_SET(fd, &wrfds);
          ret = cpnio_select (fd + 1, NULL, &wrfds, NULL, &timeo);
          if (ret == -1)
            {
              JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
              return JNI_FALSE;
            }
          if (ret == 0) /* connect timed out */
            {
              JCL_ThrowException (env, SOCKET_TIMEOUT_EXCEPTION,
                                  "connect timed out");
              return JNI_FALSE;
            }
          return JNI_TRUE; /* Connected! */
        }
      else if (ECONNREFUSED == errno)
        {
          JCL_ThrowException (env, CONNECT_EXCEPTION,
                              strerror (errno));
          return JNI_FALSE;
        }
      else
        {
          JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
          return JNI_FALSE;
        }
    }

  if (ret == -1)
    {
      if (EAGAIN == errno)
        return JNI_FALSE;
      else if (ECONNREFUSED == errno)
        {
          JCL_ThrowException (env, CONNECT_EXCEPTION,
                              strerror (errno));
          return JNI_FALSE;
        }
      else
        {
          JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
          return JNI_FALSE;
        }
    }

  return JNI_TRUE;
#else
  (void) fd;
  (void) addr;
  (void) port;
  (void) timeout;
  JCL_ThrowException (env, SOCKET_EXCEPTION, "IPv6 connect not supported");
  return JNI_FALSE;
#endif /* HAVE_CONNECT && HAVE_INET6 */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    getsockname
 * Signature: (ILjava/nio/ByteBuffer;)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_getsockname (JNIEnv *env, jclass clazz __attribute__((unused)),
                                         jint fd, jobject name)
{
#ifdef HAVE_GETSOCKNAME
#ifdef HAVE_INET6
  struct sockaddr_in6 *addr6;
  struct sockaddr_in6 sock_storage;
  socklen_t socklen = sizeof (struct sockaddr_in6);
#else
  struct sockaddr_in sock_storage;
  socklen_t socklen = sizeof (struct sockaddr_in);
#endif /* HAVE_INET6 */

  struct sockaddr *sockaddr = (struct sockaddr *) &sock_storage;
  struct sockaddr_in *addr4;
  int ret;
  char *nameptr = (*env)->GetDirectBufferAddress (env, name);

  ret = getsockname (fd, sockaddr, &socklen);
  if (ret == -1)
    {
      JCL_ThrowException (env, "java/net/SocketException", strerror (errno));
      return 0;
    }

  if (sockaddr->sa_family == AF_INET)
    {
      addr4 = (struct sockaddr_in *) sockaddr;
      memcpy (nameptr, &(addr4->sin_addr.s_addr), 4);
      memcpy (nameptr + 4, &(addr4->sin_port), 2);
      return 4;
    }

#ifdef HAVE_INET6
  /* IPv6 */
  if (sockaddr->sa_family == AF_INET6)
    {
      addr6 = (struct sockaddr_in6 *) sockaddr;
      memcpy (nameptr, &(addr6->sin6_addr.s6_addr), 16);
      memcpy (nameptr + 16, &(addr6->sin6_port), 2);
      return 16;
    }
#endif /* HAVE_INET6 */
  JCL_ThrowException (env, IO_EXCEPTION, "unsupported address format");
  return -1;
#else
  (void) fd;
  (void) name;
  JCL_ThrowException (env, IO_EXCEPTION, "getsockname not supported");
  return -1;
#endif /* HAVE_GETSOCKNAME */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    getpeername
 * Signature: (ILjava/nio/ByteBuffer;)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_getpeername (JNIEnv *env, jclass clazz __attribute__((unused)),
                                         jint fd, jobject name)
{
#ifdef HAVE_GETPEERNAME
#ifdef HAVE_INET6
  struct sockaddr_in6 *addr6;
  struct sockaddr_in6 sock_storage;
  socklen_t socklen = sizeof (struct sockaddr_in6);
#else
  struct sockaddr_in sock_storage;
  socklen_t socklen = sizeof (struct sockaddr_in);
#endif /* HAVE_INET6 */

  struct sockaddr *sockaddr = (struct sockaddr *) &sock_storage;
  struct sockaddr_in *addr4;
  int ret;
  char *nameptr = (*env)->GetDirectBufferAddress (env, name);
  
  ret = getpeername (fd, sockaddr, &socklen);
  if (ret == -1)
    {
      if (ENOTCONN != errno)
        JCL_ThrowException (env, "java/net/SocketException", strerror (errno));
      return 0;
    }

  if (sockaddr->sa_family == AF_INET)
    {
      addr4 = (struct sockaddr_in *) sockaddr;
      memcpy (nameptr, &(addr4->sin_addr.s_addr), 4);
      memcpy (nameptr + 4, &(addr4->sin_port), 2);
      return 4;
    }
#ifdef HAVE_INET6
  else if (sockaddr->sa_family == AF_INET6)
    {
      addr6 = (struct sockaddr_in6 *) sockaddr;
      memcpy (nameptr, &(addr6->sin6_addr.s6_addr), 16);
      memcpy (nameptr + 16, &(addr6->sin6_port), 2);
      return 16;
    }
#endif /* HAVE_INET6 */

  JCL_ThrowException (env, "java/net/SocketException",
                      "unsupported address type");
  return -1;
#else
  (void) fd;
  (void) name;
  JCL_ThrowException (env, IO_EXCEPTION, "getpeername not supported");
  return -1;
#endif /* HAVE_GETPEERNAME */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    accept
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_accept (JNIEnv *env,
                                    jclass c __attribute__((unused)),
                                    jint fd)
{
#ifdef HAVE_ACCEPT
  int ret;
  int tmp_errno = 0;

#ifdef HAVE_INET6
  struct sockaddr_in6 addr;
  socklen_t alen = sizeof (struct sockaddr_in6);
#else
  struct sockaddr_in addr;
  socklen_t alen = sizeof (struct sockaddr_in);
#endif /* HAVE_INET6 */

  do
    {
      ret = cpnio_accept (fd, (struct sockaddr *) &addr, &alen);
      tmp_errno = errno;
      
      if (ret == -1)
        switch (tmp_errno)
        {
          case EINTR:
            /* Check if interrupted by Thread.interrupt(). If not then some
             * other unrelated signal interrupted the system function and
             * we should start over again.
             */
            if (JCL_thread_interrupted(env))
              {
                JCL_ThrowException (env, "java/net/SocketException", strerror (tmp_errno));
                return -1;
              }
            break;
#if defined(EWOULDBLOCK) && defined(EAGAIN) && EWOULDBLOCK != EAGAIN
          case EWOULDBLOCK:
#endif
          case EAGAIN:
            if (!is_non_blocking_fd(fd))
              {
                JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "Accept timed out");
              }
            /* Socket in non-blocking mode and no pending connection. */
            return -1;
          default:
            JCL_ThrowException (env, "java/net/SocketException", strerror (tmp_errno));
            return -1;
        }
      else
        break;
    }
  while (1);

  cpio_closeOnExec(ret);

  return ret;
#else
  (void) fd;
  JCL_ThrowException (env, IO_EXCEPTION, "accept not supported");
  return -1;
#endif /* HAVE_ACCEPT */
}



/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    disconnect
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_disconnect (JNIEnv *env,
                                        jclass c __attribute__((unused)),
                                        jint fd)
{
  struct sockaddr sockaddr;

  sockaddr.sa_family = AF_UNSPEC;
  if (connect (fd, &sockaddr, sizeof (struct sockaddr)) == -1)
    {
      /* The expected error for a successful disconnect is EAFNOSUPPORT. */
      if (errno != EAFNOSUPPORT)
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
    }
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    close
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_close (JNIEnv *env,
                                   jclass c __attribute__((unused)),
                                   jint fd)
{
  if (close (fd) == -1)
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    available
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_available (JNIEnv *env,
                                       jclass c __attribute__((unused)),
                                       jint fd)
{
#if defined (FIONREAD)

  jint avail = 0;

#if defined(ENOTTY) && defined(HAVE_FSTAT)
  struct stat statBuffer;
  off_t n;
#endif

/*   NIODBG("fd: %d", fd); */
  if (ioctl (fd, FIONREAD, &avail) == -1)
    {
#if defined(ENOTTY) && defined(HAVE_FSTAT)
      if (errno == ENOTTY)
        {
          if ((fstat (fd, &statBuffer) == 0) && S_ISREG (statBuffer.st_mode))
            {
              n = lseek (fd, 0, SEEK_CUR);
              if (n != -1)
                {
                  avail = statBuffer.st_size - n;
                  return avail;
                }
            }
        }
#endif
      JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
    }
/*   NIODBG("avail: %d", avail); */

  return avail;

#elif defined(HAVE_FSTAT)

  jint avail = 0;

  struct stat statBuffer;
  off_t n;

  if ((fstat (fd, &statBuffer) == 0) && S_ISREG (statBuffer.st_mode))
    {
      n = lseek (fd, 0, SEEK_CUR);
      if (n != -1) 
        { 
	  avail = statBuffer.st_size - n;
	  return avail;
        } 
    }
  JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));

#elif defined(HAVE_SELECT)

  jint avail = 0;
  fd_set filedescriptset;
  struct timeval tv;

  FD_ZERO (&filedescriptset);
  FD_SET (fd,&filedescriptset);
  memset (&tv, 0, sizeof(tv));

  switch (select (fd+1, &filedescriptset, NULL, NULL, &tv))
    {
      case -1:
        break;
      case  0:
        avail = 0;
	return avail;
      default:
        avail = 1;
	return avail;
    }
  JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));

#else

  JCL_ThrowException (env, IO_EXCEPTION, "No native method for available");

#endif
}


enum FileChannel_mode {
  CPNIO_READ   = 1,
  CPNIO_WRITE  = 2,
  CPNIO_APPEND = 4,
  CPNIO_EXCL   = 8,
  CPNIO_SYNC   = 16,
  CPNIO_DSYNC  = 32
};


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    open
 * Signature: (Ljava/lang/String;I)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_nio_VMChannel_open (JNIEnv *env,
                                  jclass c __attribute__((unused)),
                                  jstring path, jint mode)
{
  int nmode = 0;
  int ret;
  const char *npath;

  if ((mode & CPNIO_READ) && (mode & CPNIO_WRITE))
    nmode = O_RDWR;
  else if (mode & CPNIO_WRITE)
    nmode = O_WRONLY;
  else
    nmode = O_RDONLY;

  nmode = (nmode
           | ((nmode == O_RDWR || nmode == O_WRONLY) ? O_CREAT : 0)
           | ((mode & CPNIO_APPEND) ? O_APPEND :
              ((nmode == O_WRONLY) ? O_TRUNC : 0))
           | ((mode & CPNIO_EXCL) ? O_EXCL : 0)
           | ((mode & CPNIO_SYNC) ? O_SYNC : 0));

  npath = JCL_jstring_to_cstring (env, path);

/*   NIODBG("path: %s; mode: %x", npath, nmode); */

  ret = open (npath, nmode, 0666);

/*   NIODBG("ret: %d\n", ret); */

  JCL_free_cstring (env, path, npath);

  if (-1 == ret)
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));

  return ret;
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    position
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL
Java_gnu_java_nio_VMChannel_position (JNIEnv *env,
                                      jclass c __attribute__((unused)),
                                      jint fd)
{
#ifdef HAVE_LSEEK
  off_t ret;

  ret = lseek (fd, 0, SEEK_CUR);

  if (-1 == ret)
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));

  return (jlong) ret;
#else
  JCL_ThrowException (env, IO_EXCEPTION, "position not supported");
  return -1;
#endif /* HAVE_LSEEK */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    seek
 * Signature: (IJ)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_seek (JNIEnv *env,
                                  jclass c __attribute__((unused)),
                                  jint fd, jlong pos)
{
#ifdef HAVE_LSEEK
  if (lseek (fd, (off_t) pos, SEEK_SET) == -1)
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#else
  JCL_ThrowException (env, IO_EXCEPTION, "seek not supported");
#endif /* HAVE_LSEEK */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    truncate
 * Signature: (IJ)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_truncate (JNIEnv *env,
                                      jclass c __attribute__((unused)),
                                      jint fd, jlong len)
{
#if defined(HAVE_FTRUNCATE) && defined(HAVE_LSEEK)
  off_t pos = lseek (fd, 0, SEEK_CUR);
  if (pos == -1)
    {
      JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return;
    }
  if (ftruncate (fd, (off_t) len) == -1)
    {
      JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return;
    }
  if (pos > len)
    {
      if (lseek (fd, len, SEEK_SET) == -1)
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
    }
#else
  JCL_ThrowException (env, IO_EXCEPTION, "truncate not supported");
#endif /* HAVE_FTRUNCATE && HAVE_LSEEK */
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    lock
 * Signature: (IJJZZ)Z
 */
JNIEXPORT jboolean JNICALL
Java_gnu_java_nio_VMChannel_lock (JNIEnv *env,
                                  jclass c __attribute__((unused)),
                                  jint fd, jlong pos, jlong len,
                                  jboolean shared, jboolean wait)
{
#if HAVE_FCNTL
  struct flock fl;

  fl.l_start  = (off_t) pos;
  /* Long.MAX_VALUE means lock everything possible starting at pos. */
  if (len == 9223372036854775807LL)
    fl.l_len = 0;
  else
    fl.l_len = (off_t) len;
  fl.l_pid    = getpid ();
  fl.l_type   = (shared ? F_RDLCK : F_WRLCK);
  fl.l_whence = SEEK_SET;

  if (cpnio_fcntl (fd, (wait ? F_SETLKW : F_SETLK), (long) &fl) == -1)
    {
      if (errno != EAGAIN)
        JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return JNI_FALSE;
    }

  return JNI_TRUE;
#else
  JCL_ThrowException (env, IO_EXCEPTION, "lock not supported");
  return JNI_FALSE;
#endif /* HAVE_FCNTL */
}

/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    unlock
 * Signature: (IJJ)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_unlock (JNIEnv *env,
                                    jclass c __attribute__((unused)),
                                    jint fd, jlong pos, jlong len)
{
#if HAVE_FCNTL
  struct flock fl;

  fl.l_start  = (off_t) pos;
  fl.l_len    = (off_t) len;
  fl.l_pid    = getpid ();
  fl.l_type   = F_UNLCK;
  fl.l_whence = SEEK_SET;

  if (cpnio_fcntl (fd, F_SETLK, (long) &fl) == -1)
    {
      JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
    }
#else
  JCL_ThrowException (env, IO_EXCEPTION, "unlock not supported");
#endif /* HAVE_FCNTL */
}

/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    size
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL
Java_gnu_java_nio_VMChannel_size (JNIEnv *env,
                                  jclass c __attribute__((unused)),
                                  jint fd)
{
#ifdef HAVE_FSTAT
  struct stat st;

  if (fstat (fd, &st) == -1)
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));

  return (jlong) st.st_size;
#else
  JCL_ThrowException (env, IO_EXCEPTION, "size not supported");
  return 0;
#endif
}

/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    map
 * Signature: (ICJI)Lgnu/classpath/Pointer;
 */
JNIEXPORT jobject JNICALL
Java_gnu_java_nio_VMChannel_map (JNIEnv *env,
                                 jclass clazz __attribute__((unused)),
                                 jint fd, jchar mode, jlong position, jint size)
{
#ifdef HAVE_MMAP
  jclass MappedByteBufferImpl_class;
  jmethodID MappedByteBufferImpl_init = NULL;
  jobject Pointer_instance;
  volatile jobject buffer;
  long pagesize;
  int prot, flags;
  void *p;
  void *address;

/*   NIODBG("fd: %d; mode: %x; position: %lld; size: %d", */
/*          fd, mode, position, size); */

  /* FIXME: should we just assume we're on an OS modern enough to
     have 'sysconf'? And not check for 'getpagesize'? */
#if defined(HAVE_GETPAGESIZE)
  pagesize = getpagesize ();
#elif defined(HAVE_SYSCONF)
  pagesize = sysconf (_SC_PAGESIZE);
#else
  JCL_ThrowException (env, IO_EXCEPTION,
		      "can't determine memory page size");
  return NULL;
#endif /* HAVE_GETPAGESIZE/HAVE_SYSCONF */

  if ((*env)->ExceptionOccurred (env))
    {
      return NULL;
    }

  prot = PROT_READ;
  if (mode == '+' || mode == 'c')
    {
      /* When writing we need to make sure the file is big enough,
         otherwise the result of mmap is undefined. */
      struct stat st;
      if (fstat (fd, &st) == -1)
        {
          JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
          return NULL;
        }
      if (position + size > st.st_size)
        {
          if (ftruncate(fd, position + size) == -1)
            {
              JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
              return NULL;
            }
        }
      prot |= PROT_WRITE;
    }

  flags = (mode == 'c' ? MAP_PRIVATE : MAP_SHARED);
  p = mmap (NULL, (size_t) ALIGN_UP (size, pagesize), prot, flags,
	    fd, ALIGN_DOWN (position, pagesize));
  if (p == MAP_FAILED)
    {
      JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return NULL;
    }

  /* Unalign the mapped value back up, since we aligned offset
     down to a multiple of the page size. */
  address = (void *) ((char *) p + (position % pagesize));

  Pointer_instance = JCL_NewRawDataObject(env, address);

  MappedByteBufferImpl_class = (*env)->FindClass (env,
						  "java/nio/MappedByteBufferImpl");
  if (MappedByteBufferImpl_class != NULL)
    {
      MappedByteBufferImpl_init =
	(*env)->GetMethodID (env, MappedByteBufferImpl_class,
			     "<init>", "(Lgnu/classpath/Pointer;IZ)V");
    }

  if ((*env)->ExceptionOccurred (env))
    {
      munmap (p, ALIGN_UP (size, pagesize));
      return NULL;
    }
  if (MappedByteBufferImpl_init == NULL)
    {
      JCL_ThrowException (env, "java/lang/InternalError",
                          "could not get MappedByteBufferImpl constructor");
      munmap (p, ALIGN_UP (size, pagesize));
      return NULL;
    }

  buffer = (*env)->NewObject (env, MappedByteBufferImpl_class,
                              MappedByteBufferImpl_init, Pointer_instance,
                              (jint) size, mode == 'r');
  return buffer;
#else
  (void) fd;
  (void) mode;
  (void) position;
  (void) size;
  JCL_ThrowException (env, IO_EXCEPTION,
		      "memory-mapped files not implemented");
  return 0;
#endif /* HAVE_MMAP */
}

/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    flush
 * Signature: (IZ)Z
 */
JNIEXPORT jboolean JNICALL
Java_gnu_java_nio_VMChannel_flush (JNIEnv *env,
                                   jclass c __attribute__((unused)),
                                   jint fd, jboolean metadata __attribute__((unused)))
{
#ifdef HAVE_FSYNC
  /* XXX blocking? */
  if (fsync (fd) == -1)
    {
      JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
      return JNI_FALSE;
    }
  return JNI_TRUE;
#else
  JCL_ThrowException (env, IO_EXCEPTION, "flush not implemented");
  return JNI_TRUE;
#endif /* HAVE_FSYNC */
}


#ifdef __cplusplus
}
#endif
