/* 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. */


#include <config.h>

#include <config-int.h>

#include <sys/types.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#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 "cpnative.h"
#include "cpio.h"
#include "cpnet.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 DWORD sock_receive(SOCKET sock, void* buff, int len, int* bytesReceived, struct sockaddr* from, socklen_t* fromlen);
static DWORD sock_send(SOCKET sock, void* buff, int len, int* bytesWritten, const struct sockaddr* to, socklen_t tolen);

static DWORD sock_receivev(SOCKET sock, const struct iovec* vec, int len, int* bytesReceived, struct sockaddr* from, socklen_t* fromlen);
static DWORD sock_sendv(SOCKET sock, const struct iovec* vec, int len, int* bytesWritten, const struct sockaddr* to, socklen_t tolen);

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;

// Windows Mobileでは、ネイティブメソッド側でファイル／ソケットの区別が必要
static int g_Kind_FILE;
static int g_Kind_SOCK_STREAM;
static int g_Kind_SOCK_DGRAM;

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;
}

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)); */
// ToDo:サポートする
  // return fileno (stdin);
	return (jint) INVALID_HANDLE_VALUE;
}


/*
 * 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)); */
// ToDo:サポートする
//  return fileno (stdout);
	return (jint) INVALID_HANDLE_VALUE;
}


/*
 * 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)); */
// ToDo:サポートする
//  return fileno (stderr);
	return (jint) INVALID_HANDLE_VALUE;
}


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");

  {
	  jclass kind_class = JCL_FindClass(env, "gnu/java/nio/VMChannel$Kind");
	  jfieldID fid = (*env)->GetStaticFieldID(env, kind_class, "FILE", "I");
	  g_Kind_FILE = (*env)->GetStaticIntField(env, kind_class, fid);
	  fid = (*env)->GetStaticFieldID(env, kind_class, "SOCK_STREAM", "I");
	  g_Kind_SOCK_STREAM = (*env)->GetStaticIntField(env, kind_class, fid);
	  fid = (*env)->GetStaticFieldID(env, kind_class, "SOCK_DGRAM", "I");
	  g_Kind_SOCK_DGRAM = (*env)->GetStaticIntField(env, kind_class, fid);

  }
}

JNIEXPORT void JNICALL 
Java_gnu_java_nio_VMChannel_setBlocking (JNIEnv *env, 
	jobject o __attribute__ ((__unused__)), 
	jint fd, 
	jboolean blocking)
{
	// ToDo: socketについてはノンブロッキングモードをサポートする
	if (blocking == JNI_FALSE) {
       JCL_ThrowException(env, IO_EXCEPTION, "Non-blocking mode not supported");
	}
}

/* Return true if fd is in non-blocking mode. */
static jboolean
is_non_blocking_fd(jint fd)
{
    // ToDo: Socketについてはノンブロッキングモードをサポートする
	return JNI_FALSE;
}

/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    read
 * Signature: (IILjava/nio/ByteBuffer;)I
 */
JNIEXPORT jint JNICALL Java_gnu_java_nio_VMChannel_read__IILjava_nio_ByteBuffer_2
  (JNIEnv *env, jclass clazz, jint fd, jint kind, jobject bbuf)
{

	jint len;
	jint result;
	struct JCL_buffer buf;
	DWORD dwError = NOERROR;

	// バッファを初期化する
	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;
	}
  
	// FILEとSOCKETの違いを区別する必要がある
	if (kind == g_Kind_FILE) {
		// ファイル
		if (! ReadFile((HANDLE) fd, &(buf.ptr[buf.position + buf.offset]), len, (LPDWORD) &result, NULL)) {
			dwError = GetLastError();
		}
	} else {
		// ソケット
		dwError = sock_receive((SOCKET) fd, &(buf.ptr[buf.position + buf.offset]), len, &result, NULL, NULL);
	}

	if (result <= 0) {
		// 1バイトも読み込めなかった
		result = -1;
		buf.count = 0;
	} else {
		// 読み込んだバイト数を設定
		buf.count = result;
	}
	JCL_release_buffer(env, &buf, bbuf, 0);

	// エラーに応じた例外をThrowする
	switch (dwError) {
		case NOERROR:
			break;

		case WSAETIMEDOUT:
			// タイムアウト発生
			JCL_ThrowException (env, SOCKET_TIMEOUT_EXCEPTION, "Read timed out");
			break;

		default:
			// その他のエラー
			JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, dwError);
			break;
	}

	return result;
}

/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    write
 * Signature: (IILjava/nio/ByteBuffer;)I
 */
JNIEXPORT jint JNICALL Java_gnu_java_nio_VMChannel_write__IILjava_nio_ByteBuffer_2
  (JNIEnv *env, jobject o, jint fd, jint kind, jobject bbuf)
{
	jint len;
	struct JCL_buffer buf;
	DWORD dwBytesWritten;
	DWORD dwError = NOERROR;
	int result;

	// バッファを初期化する
	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;
	}

	// 書き込み処理
	if (kind == g_Kind_FILE) {
		// ファイルの場合
		if (! WriteFile((HANDLE) fd, &(buf.ptr[buf.position + buf.offset]), len, &dwBytesWritten, NULL)) {
			dwError = GetLastError();
		}
	} else {
		// ソケットの場合
		dwError = sock_send((SOCKET) fd, &(buf.ptr[buf.position + buf.offset]), len, &dwBytesWritten, NULL, 0);
	}

	if (dwBytesWritten == 0) {
		// 1バイトも書き込めなかった
		result = -1;
		buf.count = 0;

	} else if (dwError != NOERROR) {
		// 何らかのエラーが発生
		buf.count = 0;
		if (kind != g_Kind_FILE && dwError == WSAETIMEDOUT) {
			// ソケットのタイムアウト（タイムアウトはソケットの場合のみ発生する）
			JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
			JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "Write timed out");
			return -1;

		} else {
			// その他のエラー
			JCL_release_buffer(env, &buf, bbuf, JNI_ABORT);
			JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, dwError);
			return -1;
		}
	} else {
		// エラーなし
		// 読み込んだバイト数を設定
		buf.count = dwBytesWritten;
	}
	JCL_release_buffer(env, &buf, bbuf, 0);
	return result;
}

/*
 * 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.
 *
 * Class:     gnu_java_nio_VMChannel
 * Method:    readScattering
 * Signature: (II[Ljava/nio/ByteBuffer;II)J
 */
JNIEXPORT jlong JNICALL Java_gnu_java_nio_VMChannel_readScattering
  (JNIEnv *env, jclass o, jint fd, jint kind, 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;

/*   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);
    }
    
  if (kind == g_Kind_FILE) {
	  // ファイル
	  result = cpnio_readv (fd, buffers, vec_len);
	  bytes_read = (jlong) result;
	  /* Handle the response */
	  if (result < 0) {
		JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_read);
		JCL_ThrowException (env, IO_EXCEPTION, strerror(errno));
		return -1;
	  } else if (result == 0) { /* EOF */
		result = -1;
	  }
  } else {
	  // ソケット
	  DWORD dwError = sock_receivev((SOCKET) fd, buffers, vec_len, &bytes_read, NULL, NULL);
	  if (dwError != NOERROR) {
	    bytes_read = 0;
		JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset, bytes_read);
		JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, dwError);
		return -1;
	  } else if (bytes_read == 0) { // EOF
	    result = -1;
	  } else {
	    result = bytes_read;
	  }
  }
    
  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.
 *
 * Class:     gnu_java_nio_VMChannel
 * Method:    writeGathering
 * Signature: (II[Ljava/nio/ByteBuffer;II)J
 */
JNIEXPORT jlong JNICALL Java_gnu_java_nio_VMChannel_writeGathering
  (JNIEnv *env, jobject o, jint fd, jint kind, 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;
  
/*   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);
    }
    
  if (kind == g_Kind_FILE) {
	  result = cpnio_writev (fd, buffers, vec_len);
	  bytes_written = (jlong) result;
	  if (result < 0) {
		  bytes_written = 0;
		  JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset,
							  bytes_written);
		  JCL_ThrowException (env, IO_EXCEPTION, strerror(errno));
		  return -1;
	  }
  } else {
	  // ソケット
	  DWORD dwError = sock_sendv((SOCKET) fd, buffers, vec_len, &bytes_written, NULL, 0);
	  if (dwError != NOERROR) {
		  bytes_written = 0;
		  JCL_cleanup_buffers(env, bi_list, vec_len, bbufs, offset,
							  bytes_written);
		  JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, dwError);
		  result = -1;
	  } else {
		  result = bytes_written;
	  }
  }
  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)
{
	char *addrPortPtr = (*env)->GetDirectBufferAddress (env, addrPort);
	struct JCL_buffer buf;
	struct sockaddr_in sock_storage;
	socklen_t slen = sizeof (struct sockaddr_in);
	struct sockaddr *sockaddr = (struct sockaddr *) &sock_storage;
	struct sockaddr_in *sock4;
	int ret;
	jint result = -1;
	DWORD dwError;

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


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

	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;
	
	} 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);

	switch (dwError) {
		case NOERROR:
			break;
		
		case WSAETIMEDOUT:
			// タイムアウト
            JCL_ThrowException (env, "java/net/SocketTimeoutException",
                                "read timed out");
	}
	
	return result;
}


/*
 * 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)
{
	struct sockaddr_in sockaddr;
	jbyte *elems;
	struct JCL_buffer buf;
	int ret;
	DWORD dwError;

	if (JCL_init_buffer (env, &buf, src) == -1) {
		JCL_ThrowException (env, IO_EXCEPTION, "loading buffer failed");
		return -1;
	}
	elems = (*env)->GetByteArrayElements (env, addr, NULL);

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


	dwError = sock_send((SOCKET) fd,
						&(buf.ptr[buf.position + buf.offset]),
                        buf.limit - buf.position,
						&ret,
                        (struct sockaddr *) &sockaddr,
                        sizeof (struct sockaddr_in));

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

	if (dwError == WSAETIMEDOUT) {
		// タイムアウト
		JCL_release_buffer(env, &buf, src, JNI_ABORT);
		JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "Send timed out");
		return 0;
	} else {
		JCL_release_buffer (env, &buf, src, JNI_ABORT);
		JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, dwError);
		return 0;
    }

	buf.count += ret;
	JCL_release_buffer (env, &buf, src, JNI_ABORT);
	
	return ret;
}


/*
 * 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: (II)I
 */
JNIEXPORT jint JNICALL Java_gnu_java_nio_VMChannel_read__II
  (JNIEnv *env, jclass clazz, jint fd, jint kind)
{
	int ret;
	char buf[1];

	// Windows CEではFILEとSocketを使い分ける必要がある
	if (kind == g_Kind_FILE) {
		if (! ReadFile((HANDLE) fd, buf, sizeof(buf), &ret, NULL)) {
			JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
			return -1;
		}
	} else {
		DWORD dwError = sock_receive((SOCKET) fd, buf, sizeof(buf), &ret, NULL, NULL);
		switch (dwError) {
			case NOERROR:
				break;

			case WSAETIMEDOUT:
				// タイムアウト発生
				JCL_ThrowException (env, SOCKET_TIMEOUT_EXCEPTION, "Read timed out");
				return JNI_FALSE;
			
			default:
				// その他のエラー
				JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, dwError);
				return JNI_FALSE;
		}
	}				
	if (0 == ret) {
		return -1;
	}

	return buf[0] & 0xFF;
}


/*
 * Class:     gnu_java_nio_VMChannel
 * Method:    write
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_nio_VMChannel_write__III (JNIEnv *env,
                                       jclass c __attribute__((unused)),
                                       jint fd,
									   jint kind,
									   jint data)
{
#ifdef _WIN32_WCE
	char buffer[1];
	buffer[0] = (char) data;
	if (kind == g_Kind_FILE) {
		DWORD dwBytesWritten;
		if (! WriteFile((HANDLE) fd, buffer, 1, &dwBytesWritten, NULL)) {
			JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
		}
	} else {
		// ソケット
		if (SOCKET_ERROR == send((SOCKET) fd, buffer, 1, 0)) {
			JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
		}
	}

#else

#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 */

#endif /* _WIN32_WCE */
}


/*
 * 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)
{
	int ret;
	ret = socket(AF_INET, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
	if (ret == SOCKET_ERROR) {
		JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
	}
	return ret;
}


/*
 * 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)
{
	struct sockaddr_in sockaddr;
	jbyte *addr_elems;
	int ret;
	u_long arg = 0;

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

	// ノンブロッキングモードに変更する
	arg = 1;
	if (ioctlsocket((SOCKET) fd, FIONBIO, &arg) == SOCKET_ERROR) {
		// ノンブロッキングモードにできなかった
		JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
		return JNI_FALSE;
	}

	// connect処理を開始する
	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);

	ret = connect(fd, (struct sockaddr *) &sockaddr, sizeof (struct sockaddr_in));

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

	if (ret == SOCKET_ERROR) {
		// 即時接続できなかったので、タイムアウトまで待つ
		// この呼び出し中に、ブロッキングモードに戻される
		ret = cpnet_waitForConnect((SOCKET) fd, timeout);
		switch (ret) {
			case NOERROR:
				break;
			case WSAETIMEDOUT:
				// タイムアウト
				JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "Connect timed out");
				return JNI_FALSE;
			default:
				// その他のエラー
				JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, ret);
				return JNI_FALSE;
		}
	} else {
		// 即時接続できた場合にはブロッキングモードに戻す
		arg = 0;
		if (ioctlsocket((SOCKET) fd, FIONBIO, &arg) == SOCKET_ERROR) {
			// ブロッキングモードにできなかった
			JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
			return JNI_FALSE;
		}
	}
	return JNI_TRUE;
}


/*
 * 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 0
  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;
            }
        }
    }
#endif

  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 0
  /* 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;
        }
    }
#endif

  if (ret == -1)
    {
#ifndef _WIN32_WCE
      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));
#else
		  JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
#endif
          return JNI_FALSE;
#ifndef _WIN32_WCE
        }
#endif
    }

  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)
    {
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, "java/net/SocketException", WSAGetLastError());
#else
      JCL_ThrowException (env, "java/net/SocketException", strerror (errno));
#endif
      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);
#ifdef _WIN32_WCE
	if (ret == SOCKET_ERROR) {
		DWORD dwError = WSAGetLastError();
		if (WSAENOTCONN != dwError) {
			JCL_ThrowExceptionBasedOnErrorCode(env, "java/net/SocketException", dwError);
		}
		return 0;
	}
#else
  if (ret == -1)
    {
      if (ENOTCONN != errno)
        JCL_ThrowException (env, "java/net/SocketException", strerror (errno));
      return 0;
    }
#endif

  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)
{
	int ret;
	int accept_result = cpnet_waitForAcceptable((SOCKET) fd);
	if (NOERROR != accept_result) {
		if (accept_result == WSAETIMEDOUT) {
			// タイムアウト発生
			JCL_ThrowException(env, SOCKET_TIMEOUT_EXCEPTION, "Accept timed out");
			return -1;
		} else {
			// その他のエラー
			JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, accept_result);
		}
	} else {
		// accept()可能
		struct sockaddr_in addr;
		socklen_t alen;
		ret = accept((SOCKET) fd, (struct sockaddr*) &addr, &alen);
		if (ret == SOCKET_ERROR) {
			JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
			return -1;
		}
	}
	return ret;
}



/*
 * 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;
  connect (fd, &sockaddr, sizeof (struct sockaddr));
}


/*
 * 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,
								   jint kind)
{
	if (kind == g_Kind_FILE) {
		CloseHandle((HANDLE) fd);
	} else {
		shutdown((SOCKET) fd, SD_SEND);
		cpnet_close(env, fd);
	}
}


/*
 * 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,
									   jint kind)
{
	if (kind == g_Kind_FILE) {
		jlong avail;
		if (CPNATIVE_OK == cpio_availableBytes(fd, &avail)) {
			return (jint) avail;
		}
		return -1;
	} else {
		jint avail = 0;
		if (ioctlsocket((SOCKET) fd, FIONREAD, &avail) == SOCKET_ERROR) {
			JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
		}
		return avail;
	}
}


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)
{
	DWORD dwDesiredAccess = 0;
	DWORD dwCreationDisposition = 0;
	_TCHAR filename[MAX_PATH];
	HANDLE ret;
	const jchar* rawstr = (*env)->GetStringChars(env, path, 0);
	jint namelen = (*env)->GetStringLength(env, path);

	namelen = namelen <= MAX_PATH ? namelen : MAX_PATH;
	_tcsncpy(filename, rawstr, namelen);
	filename[namelen] = _T('\0');
	(*env)->ReleaseStringChars(env, path, rawstr);
	
	if ((mode & CPNIO_READ) && (mode & CPNIO_WRITE)) {
		// 読み書き両用（ファイルが存在しない場合は新規作成）
		dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
		dwCreationDisposition = OPEN_ALWAYS;
	} else if (mode & CPNIO_WRITE) {
		// 書き込み専用（ファイルは常に作成）
		dwDesiredAccess = GENERIC_WRITE;
		dwCreationDisposition = CREATE_ALWAYS;
	} else {
		// 読み取り専用（存在しない場合はエラーになる）
		dwDesiredAccess = GENERIC_READ;
		dwCreationDisposition = OPEN_EXISTING;
	}
	
	ret = CreateFile(filename,
					 dwDesiredAccess,
					 FILE_SHARE_READ | FILE_SHARE_WRITE,
					 NULL,
					 dwCreationDisposition,
					 FILE_ATTRIBUTE_NORMAL,
					 NULL);

	if (ret == INVALID_HANDLE_VALUE) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
	}

	return (jint) 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)
{
	DWORD result = SetFilePointer((HANDLE) fd, 0, NULL, FILE_CURRENT);
	if (result == INVALID_SET_FILE_POINTER) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
	}
	return (jlong) result;
}


/*
 * 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)
{
	if (INVALID_SET_FILE_POINTER == SetFilePointer((HANDLE) fd, (LONG) pos, NULL, FILE_BEGIN)) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
	}
}


/*
 * 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)
{
	DWORD dwCurrent = SetFilePointer((HANDLE) fd, 0, NULL, FILE_CURRENT);
	if (INVALID_SET_FILE_POINTER == dwCurrent) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
		return;
	}
	if (INVALID_SET_FILE_POINTER == SetFilePointer((HANDLE) fd, (LONG) len, NULL, FILE_BEGIN)) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
		return;
	}

	if (! SetEndOfFile((HANDLE) fd)) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
		return;
	}
	
	if (INVALID_SET_FILE_POINTER == SetFilePointer((HANDLE) fd, dwCurrent, NULL, FILE_BEGIN)) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
		return;
	}
}


/*
 * 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 _WIN32_WCE
	typedef BOOL (*LOCK_FILE_EX)(HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED);
	HANDLE hFile = (HANDLE) fd;
	HMODULE hModule = GetModuleHandle(_T("coredll"));
	DWORD dwFlags = 0;
	LOCK_FILE_EX lock_file_ex;

#ifndef LOCKFILE_FAIL_IMMEDIATELY
#define LOCKFILE_FAIL_IMMEDIATELY 0x00000001
#endif

#ifndef LOCKFILE_EXCLUSIVE_LOCK
#define LOCKFILE_EXCLUSIVE_LOCK   0x00000002
#endif

	if (hModule) {
		// LockFileEx()のポインタを得る
		lock_file_ex = (LOCK_FILE_EX) GetProcAddress(hModule, _T("LockFileEx"));
		if (lock_file_ex) {
			OVERLAPPED overlapped = {0};
			overlapped.Offset = (DWORD) pos;
			overlapped.OffsetHigh = (DWORD) (pos >> 32);

			if (! shared) {
				dwFlags |= LOCKFILE_EXCLUSIVE_LOCK;
			}
			if (! wait) {
				dwFlags |= LOCKFILE_FAIL_IMMEDIATELY;
			}
			if (! lock_file_ex(hFile, dwFlags, 0,(DWORD) len, (DWORD) (len >> 32) , &overlapped)) {
				JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
				return JNI_FALSE;
			}
		}
	}
	if (! hModule || ! lock_file_ex) {
	  JCL_ThrowException (env, IO_EXCEPTION,
						  "LockFileEx() is not supported on this platform");
	  return JNI_FALSE;
	}
	return JNI_TRUE;

#else

#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 */

#endif	/* _WIN32_WCE */
}

/*
 * 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)
{
#ifdef _WIN32_WCE
	HANDLE hFile = (HANDLE) fd;
	typedef BOOL (*UNLOCK_FILE_EX)(HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED);
	HMODULE hModule = GetModuleHandle(_T("coredll"));
	DWORD dwFlags = 0;
	UNLOCK_FILE_EX unlock_file_ex;

	if (hModule) {
		unlock_file_ex = (UNLOCK_FILE_EX) GetProcAddress(hModule, _T("UnlockFileEx"));
		if (unlock_file_ex) {
			OVERLAPPED overlapped = {0};
			overlapped.Offset = (DWORD) pos;
			overlapped.OffsetHigh = (DWORD) (pos >> 32);
			if (! unlock_file_ex(hFile, 0, (DWORD) len, (DWORD)(len >> 32), &overlapped)) {
				JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
				return;
			}
		}
	}
	if (! hModule || ! unlock_file_ex) {
	  JCL_ThrowException (env, "java/lang/UnsupportedOperationException",
						  "file locks not implemented on this platform");
	}

	return;

#else

#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 */

#endif /* _WIN32_WCE */
}

/*
 * 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)
{
	return (jlong) (GetFileSize((HANDLE) fd, NULL) & 0xffffffff);
}

/*
 * 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)
{
  jclass MappedByteBufferImpl_class;
  jmethodID MappedByteBufferImpl_init = NULL;
  jobject Pointer_instance;
  volatile jobject buffer;
  long pagesize;
  DWORD dwDesiredAccess;
  DWORD flProtect;
  DWORD dwNumberOfBytesToMap;
  void *p;
  void *address;
  HANDLE hFile, hMapping;
  jfieldID fid;
  jlong implLen;
  __int64 mapping_size = position + size;
  OSVERSIONINFO versioninfo = {0};

  // Windows Mobile 5以降でしか動作しない
  // (Pocket PC 2003までは、CreateFileForMapping()でオープンしなければならない）
  versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&versioninfo);
  if (versioninfo.dwMajorVersion < 5) {
	JCL_ThrowException(env, IO_EXCEPTION, "only works on Windows Mobile 5 or later version");
    return NULL;
  }

  // フラグを変換する
  dwDesiredAccess = GENERIC_READ;
  flProtect = PAGE_READONLY;
  if (mode == '+') {
    dwDesiredAccess |= GENERIC_WRITE;
    flProtect = PAGE_READWRITE;
  } else if (mode == 'c') {
	// このフラグはサポートされていない
	JCL_ThrowException (env, IO_EXCEPTION,
		         "copy-on-write not supported");
	return NULL;

  }

  // フィールド"description"からファイル名を取得する
#if 0
  // GNU Classpath 0.93から上記フィールドはなくなってしまった
  fid = (*env)->GetFieldID(env,
	                       (*env)->GetObjectClass(env, obj),
						   "description",
						   "Ljava/lang/String;");
  if (! fid) {
	  JCL_ThrowException (env, IO_EXCEPTION,
		         "can't get field id of 'description'");
	  return NULL;
  }
  filename_string = (*env)->GetObjectField(env, obj, fid);
  if (! filename_string) {
	  JCL_ThrowException (env, IO_EXCEPTION,
		         "can't get value of 'description'");
	  return NULL;
  }
  tmp_filename = (*env)->GetStringChars(env, filename_string, NULL);
  filename_length = (*env)->GetStringLength(env, filename_string);
  filename = (_TCHAR*) malloc(sizeof(_TCHAR) * (filename_length + 1));
  _tcsncpy(filename, tmp_filename, filename_length);
  filename[filename_length] = _T('\0');
  (*env)->ReleaseStringChars(env, filename_string, tmp_filename);


  hFile = CreateFileForMapping(filename,
	                           dwDesiredAccess,
							   FILE_SHARE_READ,
							   NULL,
							   OPEN_EXISTING,
							   FILE_ATTRIBUTE_NORMAL,
							   NULL);
  free(filename);

  if (hFile == INVALID_HANDLE_VALUE) {
    // オープン失敗
	DWORD dwError = GetLastError();
    char msg[256];
	sprintf(msg, "CreateFileForMapping() failed. GetLastError()=%d", dwError);
    JCL_ThrowException(env, IO_EXCEPTION, msg);
	return NULL;
  }
#endif

  hFile = (HANDLE) fd;
	
  hMapping = CreateFileMapping(hFile,			// ファイルハンドル
							   NULL,			// SECURITY_ATTRIBUTE
							   flProtect,		// ページ保護
							   (DWORD) (mapping_size >> 32),	// マッピングサイズの上位32ビット
							   (DWORD) (mapping_size),			// マッピングサイズの下位32ビット
							   NULL);			// 名前なし
  if (hMapping == INVALID_HANDLE_VALUE) {
	// ファイルマッピング失敗
	DWORD dwError = GetLastError();
	JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, dwError);
	return NULL;
  }
	                         
  pagesize = getpagesize ();
  dwNumberOfBytesToMap = (DWORD) ALIGN_UP(size, pagesize);
  // ファイルサイズより大きな場合は0に補正する
  if (GetFileSize(hFile, NULL) <= dwNumberOfBytesToMap) {
	dwNumberOfBytesToMap = 0;
  }

  p = MapViewOfFile(hMapping, 
	                (flProtect == PAGE_READWRITE) ? FILE_MAP_WRITE : FILE_MAP_READ,
					(DWORD) (ALIGN_DOWN (position, pagesize) >> 32),	// 上位32ビット
					(DWORD) ALIGN_DOWN (position, pagesize),			// 下位32ビット
					dwNumberOfBytesToMap);
  if (! p) {
	// アドレスへの割り当て失敗
	DWORD dwError = GetLastError();
    CloseHandle(hMapping);
    JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, dwError);
	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))
    {
      UnmapViewOfFile(p);
	  CloseHandle(hMapping);
      return NULL;
    }
  if (MappedByteBufferImpl_init == NULL)
    {
      JCL_ThrowException (env, "java/lang/InternalError",
                          "could not get MappedByteBufferImpl constructor");
      UnmapViewOfFile(p);
	  CloseHandle(hMapping);
      return NULL;
    }

  buffer = (*env)->NewObject (env, MappedByteBufferImpl_class,
                              MappedByteBufferImpl_init, Pointer_instance,
                              (jint) size, mode == 'r');
  if (! buffer) {
	  UnmapViewOfFile(p);
	  CloseHandle(hMapping);
	  return NULL;
  }

  // フィールド "implLen"にハンドルを格納しておく
  // (MappedByteBufferImpl.unmapImpl()内で利用する）
  fid = (*env)->GetFieldID(env,
	                       MappedByteBufferImpl_class,
						   "implLen",
						   "J");
  if (! fid) {
      JCL_ThrowException (env, "java/lang/InternalError",
                          "could not get field id of MappedByteBufferImpl.implLen");
	  UnmapViewOfFile(p);
	  CloseHandle(hMapping);
	  return NULL;
  }
  implLen = (jlong) ((jlong) hMapping & 0xffffffff);
  (*env)->SetLongField(env,
	                   buffer,
					   fid,
					   implLen);

  return buffer;
}

/*
 * 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)))
{
	if (! FlushFileBuffers((HANDLE) fd)) {
		JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, GetLastError());
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

/**
 * ソケットからデータを受信し、バッファに格納する
 *
 * @param	sock	ソケット
 * @param	buff	データバッファ
 * @param	len		データバッファのサイズ
 * @param	bytesReceived	受信したデータ量（バイト単位）が返される
 * @param	from			相手のアドレス。NULLの場合にはrecv()が、NULL以外の場合にはrecvfrom()が使用される。
 * @param	fromlen			相手アドレスのサイズ。fromがNULLの場合は無視される。
 * @return	エラーコード。正常に処理が完了した場合はNOERROR。
 */
static DWORD sock_receive(SOCKET sock, void* buff, int len, int* bytesReceived, struct sockaddr* from, socklen_t* fromlen) {
	int ret = cpnet_waitForReadable(sock);
	if (NOERROR == ret) {
		if (from) {
			ret = recvfrom(sock, buff, len, 0, from, fromlen);
		} else {
			ret = recv(sock, buff, len, 0);
		}

		if (ret == SOCKET_ERROR) {
			ret = WSAGetLastError();
		} else {
			*bytesReceived = ret;
			ret = NOERROR;
		}
	}
	return ret;
}

/**
 * ソケットにデータを送信する
 *
 * @param	sock			ソケット
 * @param	buff			データバッファ
 * @param	len				データバッファのサイズ
 * @param	bytesWritten	送信したデータ量（バイト単位）が返される
 * @param	to				送信先アドレス。NULLの場合にはsend()が、NULL以外の場合にはsendto()が使用される。
 * @param	tolen			送信先アドレスのサイズ。fromがNULLの場合は無視される。
 * @return	エラーコード。正常に処理が完了した場合はNOERROR。
 */
static DWORD sock_send(SOCKET sock, void* buff, int len, int* bytesWritten, const struct sockaddr* to, socklen_t tolen) {
	int ret = cpnet_waitForWritable(sock);
	if (NOERROR == ret) {
		if (to) {
			ret = sendto(sock, buff, len, 0, to, tolen);
		} else {
			ret = send(sock, buff, len, 0);
		}
		if (ret == SOCKET_ERROR) {
			ret = WSAGetLastError();
		} else {
			*bytesWritten = ret;
			ret = NOERROR;
		}
	}
	return ret;
}

static DWORD sock_receivev(SOCKET sock,
						   const struct iovec* vec,
						   int count,
						   int* bytesReceived,
						   struct sockaddr* from,
						   socklen_t* fromlen) {

	size_t tmpbuf_len = 0;
	void* tmpbuf;
	char* charbuf;
	int i;
	DWORD dwError;

	if (count < 0) {
		*bytesReceived = 0;
		return ERROR_INVALID_PARAMETER;
	}

	// テンポラリバッファのサイズを計算する
	for (i = 0; i < count; ++i) {
		tmpbuf_len += vec[i].iov_len;
	}
	
	// テンポラリバッファを確保する
	tmpbuf = malloc(tmpbuf_len);
	if (! tmpbuf) {
		*bytesReceived = 0;
		return ERROR_NOT_ENOUGH_MEMORY;
	}

	dwError = sock_receive(sock, tmpbuf, tmpbuf_len, bytesReceived, from, fromlen);

	charbuf = (char*) tmpbuf;
	if (dwError == NOERROR) {
		// テンポラリバッファから vec にコピーする
		ssize_t left = *bytesReceived;
		for (i = 0; i < count; ++i) {
			const size_t iov_len = vec[i].iov_len;
			const size_t copy_len = (left < (ssize_t) iov_len)
						? left
						: iov_len;
			memcpy(vec[i].iov_base, charbuf, copy_len);
			charbuf += copy_len;
			left -= copy_len;
			if (left <= 0) {
				// もうコピーするデータがない
				break;
			}
		}
	}

	// テンポラリバッファを開放
	free(tmpbuf);
	return dwError;

}

static DWORD sock_sendv(SOCKET sock, const struct iovec* vec, int count, int* bytesWritten, const struct sockaddr* to, socklen_t tolen) {
	ssize_t tmpbuf_len;
	void* tmpbuf;
	char* charbuf;
	int i;
	DWORD dwError;

	if (count < 0) {
		*bytesWritten = 0;
		return ERROR_INVALID_PARAMETER;
	}

	// テンポラリバッファのサイズを計算する
	tmpbuf_len = 0;
	for (i = 0; i < count; ++i) {
		tmpbuf_len += vec[i].iov_len;
	}

	// テンポラリバッファを確保する
	tmpbuf = malloc(tmpbuf_len);
	if (! tmpbuf) {
		*bytesWritten = 0;
		return ERROR_NOT_ENOUGH_MEMORY;
	}

	// テンポラリバッファにデータをコピーする
	charbuf = (char*) tmpbuf;
	for (i = 0; i < count; ++i) {
		const size_t iov_len = vec[i].iov_len;
		memcpy(charbuf, vec[i].iov_base, iov_len);
		charbuf += iov_len;
	}

	// テンポラリバッファの内容を書き出す
	dwError = sock_send(sock, tmpbuf, tmpbuf_len, bytesWritten, to, tolen);

	// テンポラリバッファを解放する
	free(tmpbuf);

	return dwError;
}


#ifdef __cplusplus
}
#endif
