/*
 * $Id$
 *
    Copyright (c) 2016-2018 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "hcl-prv.h"
#include <hcl-utl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(_WIN32)
#	include <windows.h>
#	include <tchar.h>
#	include <time.h>
#	include <io.h>
#	include <errno.h>

#	if defined(HCL_HAVE_CFG_H) && defined(HCL_ENABLE_LIBLTDL)
#		include <ltdl.h>
#		define USE_LTDL
#	else
#		define USE_WIN_DLL
#	endif


#	include "poll-msw.h"
#	define USE_POLL
#	define XPOLLIN POLLIN
#	define XPOLLOUT POLLOUT
#	define XPOLLERR POLLERR
#	define XPOLLHUP POLLHUP


#if !defined(SIZE_T)
#	define SIZE_T unsigned long int
#endif

#elif defined(__OS2__)
#	define INCL_DOSMODULEMGR
#	define INCL_DOSPROCESS
#	define INCL_DOSSEMAPHORES
#	define INCL_DOSEXCEPTIONS
#	define INCL_DOSMISC
#	define INCL_DOSDATETIME
#	define INCL_DOSFILEMGR
#	define INCL_DOSERRORS
#	include <os2.h>
#	include <time.h>
#	include <fcntl.h>
#	include <io.h>
#	include <errno.h>

#	include <types.h> /* some types for socket.h */
#	include <sys/socket.h> /* for socketpair */
#	include <sys/time.h>
#	include <sys/ioctl.h> /* FIONBIO */
#	include <nerrno.h> /* for SOCEXXX error codes */

#	define BSD_SELECT
#	if defined(TCPV40HDRS)
#	include <sys/select.h>
#	include <sys/un.h> /* for sockaddr_un */
#	else
#	include <unistd.h>
#	endif

#	define USE_SELECT
	/* fake XPOLLXXX values */
#	define XPOLLIN  (1 << 0)
#	define XPOLLOUT (1 << 1)
#	define XPOLLERR (1 << 2)
#	define XPOLLHUP (1 << 3)

#elif defined(__DOS__)
#	include <dos.h>
#	include <time.h>
#	include <io.h>
#	include <signal.h>
#	include <errno.h>
#	include <fcntl.h>
#	include <conio.h> /* inp, outp */

#	if defined(_INTELC32_)
#		define DOS_EXIT 0x4C
#		include <i32.h>
#		include <stk.h>
#	else
#		include <dosfunc.h>
#	endif

	/* fake XPOLLXXX values */
#	define XPOLLIN  (1 << 0)
#	define XPOLLOUT (1 << 1)
#	define XPOLLERR (1 << 2)
#	define XPOLLHUP (1 << 3)

#elif defined(macintosh)
#	include <Types.h>
#	include <OSUtils.h>
#	include <Timer.h>

#	include <MacErrors.h>
#	include <Process.h>
#	include <Dialogs.h>
#	include <TextUtils.h>

#else

#	include <sys/types.h>
#	include <unistd.h>
#	include <fcntl.h>
#	include <errno.h>

#	if defined(HAVE_TIME_H)
#		include <time.h>
#	endif
#	if defined(HAVE_SYS_TIME_H)
#		include <sys/time.h>
#	endif
#	if defined(HAVE_SIGNAL_H)
#		include <signal.h>
#	endif
#	if defined(HAVE_SYS_MMAN_H)
#		include <sys/mman.h>
#	endif

#	if defined(HCL_ENABLE_LIBLTDL)
#		include <ltdl.h>
#		define USE_LTDL
#	elif defined(HAVE_DLFCN_H)
#		include <dlfcn.h>
#		define USE_DLFCN
#	elif defined(__APPLE__) || defined(__MACOSX__)
#		define USE_MACH_O_DYLD
#		include <mach-o/dyld.h>
#	else
#		error UNSUPPORTED DYNAMIC LINKER
#	endif

#	if defined(HAVE_TIME_H)
#		include <time.h>
#	endif
#	if defined(HAVE_SYS_TIME_H)
#		include <sys/time.h>
#	endif
#	if defined(HAVE_SIGNAL_H)
#		include <signal.h>
#	endif
#	if defined(HAVE_SYS_MMAN_H)
#		include <sys/mman.h>
#	endif

#	if defined(USE_THREAD)
#		include <pthread.h>
#		include <sched.h>
#	endif

#	if defined(HAVE_SYS_DEVPOLL_H)
		/* solaris */
#		include <sys/devpoll.h>
#		define USE_DEVPOLL
#		define XPOLLIN POLLIN
#		define XPOLLOUT POLLOUT
#		define XPOLLERR POLLERR
#		define XPOLLHUP POLLHUP
#	elif defined(HAVE_SYS_EVENT_H) && defined(HAVE_KQUEUE)
		/* netbsd, openbsd, etc */
#		include <sys/event.h>
#		define USE_KQUEUE
		/* fake XPOLLXXX values */
#		define XPOLLIN  (1 << 0)
#		define XPOLLOUT (1 << 1)
#		define XPOLLERR (1 << 2)
#		define XPOLLHUP (1 << 3)
#	elif defined(HAVE_SYS_EPOLL_H) && defined(HAVE_EPOLL_CREATE)
		/* linux */
#		include <sys/epoll.h>
#		define USE_EPOLL
#		define XPOLLIN EPOLLIN
#		define XPOLLOUT EPOLLOUT
#		define XPOLLERR EPOLLERR
#		define XPOLLHUP EPOLLHUP
#	elif defined(HAVE_POLL_H)
#		include <poll.h>
#		define USE_POLL
#		define XPOLLIN POLLIN
#		define XPOLLOUT POLLOUT
#		define XPOLLERR POLLERR
#		define XPOLLHUP POLLHUP
#	else
#		define USE_SELECT
		/* fake XPOLLXXX values */
#		define XPOLLIN  (1 << 0)
#		define XPOLLOUT (1 << 1)
#		define XPOLLERR (1 << 2)
#		define XPOLLHUP (1 << 3)
#	endif
#endif

#if !defined(HCL_DEFAULT_PFMODDIR)
#	define HCL_DEFAULT_PFMODDIR ""
#endif

#if !defined(HCL_DEFAULT_PFMODPREFIX)
#	if defined(_WIN32)
#		define HCL_DEFAULT_PFMODPREFIX "hcl-"
#	elif defined(__OS2__)
#		define HCL_DEFAULT_PFMODPREFIX "hcl"
#	elif defined(__DOS__)
#		define HCL_DEFAULT_PFMODPREFIX "hcl"
#	else
#		define HCL_DEFAULT_PFMODPREFIX "libhcl-"
#	endif
#endif

#if !defined(HCL_DEFAULT_PFMODPOSTFIX)
#	if defined(_WIN32)
#		define HCL_DEFAULT_PFMODPOSTFIX ""
#	elif defined(__OS2__)
#		define HCL_DEFAULT_PFMODPOSTFIX ""
#	elif defined(__DOS__)
#		define HCL_DEFAULT_PFMODPOSTFIX ""
#	else
#		if defined(USE_DLFCN)
#			define HCL_DEFAULT_PFMODPOSTFIX ".so"
#		elif defined(USE_MACH_O_DYLD)
#			define HCL_DEFAULT_PFMODPOSTFIX ".dylib"
#		else
#			define HCL_DEFAULT_PFMODPOSTFIX ""
#		endif
#	endif
#endif

#if defined(USE_THREAD)
#	define MUTEX_INIT(x) pthread_mutex_init((x), MOO_NULL)
#	define MUTEX_DESTROY(x) pthread_mutex_destroy(x)
#	define MUTEX_LOCK(x) pthread_mutex_lock(x)
#	define MUTEX_UNLOCK(x) pthread_mutex_unlock(x)
#else
#	define MUTEX_INIT(x)
#	define MUTEX_DESTROY(x)
#	define MUTEX_LOCK(x)
#	define MUTEX_UNLOCK(x)
#endif

#if defined(USE_SELECT)
struct select_fd_t
{
	int fd;
	int events;
};
#endif

typedef struct xtn_t xtn_t;
struct xtn_t
{
	hcl_t* next;
	hcl_t* prev;

	int vm_running;
	int rcv_tick;

	hcl_cmgr_t* input_cmgr;
	hcl_cmgr_t* log_cmgr;

	struct
	{
		int fd;
		int fd_flag; /* bitwise OR'ed fo logfd_flag_t bits */

		struct
		{
			hcl_bch_t buf[4096];
			hcl_oow_t len;
		} out;
	} log;

	#if defined(_WIN32)
	HANDLE waitable_timer;
	DWORD tc_last;
	DWORD tc_overflow;
	#elif defined(__OS2__)
	ULONG tc_last;
	ULONG tc_overflow;
	hcl_ntime_t tc_last_ret;
	#elif defined(__DOS__)
	clock_t tc_last;
	hcl_ntime_t tc_last_ret;
	#endif

	#if defined(USE_DEVPOLL)
	int ep; /* /dev/poll */
	#elif defined(USE_KQUEUE)
	int ep; /* kqueue */
	#elif defined(USE_EPOLL)
	int ep; /* epoll */
	#elif defined(USE_POLL)
	/* nothing */
	#elif defined(USE_SELECT)
	/* nothing */
	#endif

	#if defined(USE_THREAD)
	struct
	{
		int p[2]; /* pipe for signaling */
		pthread_t thr;
		int up;
		int abort;
	} iothr;
	#endif

	struct
	{
		int p[2]; /* pipe for signaling */
	} sigfd;

	struct
	{
	#if defined(USE_DEVPOLL)
		/*TODO: make it dynamically changeable depending on the number of
		 *      file descriptors added */
		struct pollfd buf[64]; /* buffer for reading events */
	#elif defined(USE_KQUEUE)
		struct
		{
			hcl_oow_t* ptr;
			hcl_oow_t capa;
		} reg;
		struct kevent buf[64];
	#elif defined(USE_EPOLL)
		/*TODO: make it dynamically changeable depending on the number of
		 *      file descriptors added */
		struct epoll_event buf[64]; /* buffer for reading events */
	#elif defined(USE_POLL)
		struct
		{
			struct pollfd* ptr;
			hcl_oow_t capa;
			hcl_oow_t len;
		#if defined(USE_THREAD)
			pthread_mutex_t pmtx;
		#endif
		} reg; /* registry */
		struct pollfd* buf;

	#elif defined(USE_SELECT)
		struct
		{
			fd_set rfds;
			fd_set wfds;
			int maxfd;
		#if defined(USE_THREAD)
			pthread_mutex_t smtx;
		#endif
		} reg;

		struct select_fd_t buf[FD_SETSIZE];
	#endif

		hcl_oow_t len;

	#if defined(USE_THREAD)
		pthread_mutex_t mtx;
		pthread_cond_t cnd;
		pthread_cond_t cnd2;
	#endif

		int halting;
	} ev;
};

#define GET_XTN(hcl) ((xtn_t*)((hcl_uint8_t*)hcl_getxtn(hcl) - HCL_SIZEOF(xtn_t)))


/* -----------------------------------------------------------------
 * BASIC MEMORY MANAGER
 * ----------------------------------------------------------------- */

static void* sys_alloc (hcl_mmgr_t* mmgr, hcl_oow_t size)
{
	return malloc(size);
}

static void* sys_realloc (hcl_mmgr_t* mmgr, void* ptr, hcl_oow_t size)
{
	return realloc(ptr, size);
}

static void sys_free (hcl_mmgr_t* mmgr, void* ptr)
{
	free (ptr);
}

static hcl_mmgr_t sys_mmgr =
{
	sys_alloc,
	sys_realloc,
	sys_free,
	HCL_NULL
};

/* -----------------------------------------------------------------
 * LOGGING SUPPORT
 * ----------------------------------------------------------------- */

enum logfd_flag_t
{
	LOGFD_TTY = (1 << 0),
	LOGFD_OPENED_HERE = (1 << 1)
};

static int write_all (int fd, const hcl_bch_t* ptr, hcl_oow_t len)
{
	while (len > 0)
	{
		hcl_ooi_t wr;

		wr = write(fd, ptr, len);

		if (wr <= -1)
		{
		#if defined(EAGAIN) && defined(EWOULDBLOCK) && (EAGAIN == EWOULDBLOCK)
			if (errno == EAGAIN) continue;
		#else
			#if defined(EAGAIN)
			if (errno == EAGAIN) continue;
			#elif defined(EWOULDBLOCK)
			if (errno == EWOULDBLOCK) continue;
			#endif
		#endif

		#if defined(EINTR)
			/* TODO: would this interfere with non-blocking nature of this VM? */
			if (errno == EINTR) continue;
		#endif
			return -1;
		}

		ptr += wr;
		len -= wr;
	}

	return 0;
}

static int write_log (hcl_t* hcl, int fd, const hcl_bch_t* ptr, hcl_oow_t len)
{
	xtn_t* xtn = GET_XTN(hcl);

	while (len > 0)
	{
		if (xtn->log.out.len > 0)
		{
			hcl_oow_t rcapa, cplen;

			rcapa = HCL_COUNTOF(xtn->log.out.buf) - xtn->log.out.len;
			cplen = (len >= rcapa)? rcapa: len;

			HCL_MEMCPY (&xtn->log.out.buf[xtn->log.out.len], ptr, cplen);
			xtn->log.out.len += cplen;
			ptr += cplen;
			len -= cplen;

			if (xtn->log.out.len >= HCL_COUNTOF(xtn->log.out.buf))
			{
				int n;
				n = write_all(fd, xtn->log.out.buf, xtn->log.out.len);
				xtn->log.out.len = 0;
				if (n <= -1) return -1;
			}
		}
		else
		{
			hcl_oow_t rcapa;

			rcapa = HCL_COUNTOF(xtn->log.out.buf);
			if (len >= rcapa)
			{
				if (write_all(fd, ptr, rcapa) <= -1) return -1;
				ptr += rcapa;
				len -= rcapa;
			}
			else
			{
				HCL_MEMCPY (xtn->log.out.buf, ptr, len);
				xtn->log.out.len += len;
				ptr += len;
				len -= len;
			}
		}
	}

	return 0;
}

static void flush_log (hcl_t* hcl, int fd)
{
	xtn_t* xtn = GET_XTN(hcl);
	if (xtn->log.out.len > 0)
	{
		write_all (fd, xtn->log.out.buf, xtn->log.out.len);
		xtn->log.out.len = 0;
	}
}


static void log_write (hcl_t* hcl, hcl_bitmask_t mask, const hcl_ooch_t* msg, hcl_oow_t len)
{
	hcl_bch_t buf[256];
	hcl_oow_t ucslen, bcslen, msgidx;
	int n;

	xtn_t* xtn = GET_XTN(hcl);
	int logfd;

	if (mask & HCL_LOG_STDERR) logfd = 2;
	else if (mask & HCL_LOG_STDOUT) logfd = 1;
	else
	{
		logfd = xtn->log.fd;
		if (logfd <= -1) return;
	}

/* TODO: beautify the log message.
 *       do classification based on mask. */
	if (!(mask & (HCL_LOG_STDOUT | HCL_LOG_STDERR)))
	{
		time_t now;
		char ts[32];
		size_t tslen;
		struct tm tm, *tmp;

		now = time(HCL_NULL);
	#if defined(_WIN32)
		tmp = localtime(&now);
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %z ", tmp);
		if (tslen == 0)
		{
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
		}
	#elif defined(__OS2__)
		#if defined(__WATCOMC__)
		tmp = _localtime(&now, &tm);
		#else
		tmp = localtime(&now);
		#endif

		#if defined(__BORLANDC__) || defined(__IBMC__)
		/* the borland compiler doesn't handle %z properly - it showed 00 all the time.
		 * the visualage compiler 3.0 doesn't support %z */
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %Z ", tmp);
		#else
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %z ", tmp);
		#endif
		if (tslen == 0)
		{
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
		}

	#elif defined(__DOS__)
		tmp = localtime(&now);
		/* since i know that %z/%Z is not available in strftime, i switch to sprintf immediately */
		tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
	#else
		#if defined(HAVE_LOCALTIME_R)
		tmp = localtime_r(&now, &tm);
		#else
		tmp = localtime(&now);
		#endif

		#if defined(HAVE_STRFTIME_SMALL_Z)
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %z ", tmp);
		#else
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %Z ", tmp);
		#endif
		if (tslen == 0)
		{
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
		}
	#endif
		write_log (hcl, logfd, ts, tslen);
	}

	if (logfd == xtn->log.fd && (xtn->log.fd_flag & LOGFD_TTY))
	{
		if (mask & HCL_LOG_FATAL) write_log (hcl, logfd, "\x1B[1;31m", 7);
		else if (mask & HCL_LOG_ERROR) write_log (hcl, logfd, "\x1B[1;32m", 7);
		else if (mask & HCL_LOG_WARN) write_log (hcl, logfd, "\x1B[1;33m", 7);
	}

#if defined(HCL_OOCH_IS_UCH)
	msgidx = 0;
	while (len > 0)
	{
		ucslen = len;
		bcslen = HCL_COUNTOF(buf);

		n = hcl_convootobchars(hcl, &msg[msgidx], &ucslen, buf, &bcslen);
		if (n == 0 || n == -2)
		{
			/* n = 0:
			 *   converted all successfully
			 * n == -2:
			 *    buffer not sufficient. not all got converted yet.
			 *    write what have been converted this round. */

			HCL_ASSERT (hcl, ucslen > 0); /* if this fails, the buffer size must be increased */

			/* attempt to write all converted characters */
			if (write_log(hcl, logfd, buf, bcslen) <= -1) break;

			if (n == 0) break;
			else
			{
				msgidx += ucslen;
				len -= ucslen;
			}
		}
		else if (n <= -1)
		{
			/* conversion error */
			break;
		}
	}
#else
	write_log (hcl, logfd, msg, len);
#endif

	if (logfd == xtn->log.fd && (xtn->log.fd_flag & LOGFD_TTY))
	{
		if (mask & (HCL_LOG_FATAL | HCL_LOG_ERROR | HCL_LOG_WARN)) write_log (hcl, logfd, "\x1B[0m", 4);
	}

	flush_log (hcl, logfd);
}

/* -----------------------------------------------------------------
 * SYSTEM ERROR CONVERSION
 * ----------------------------------------------------------------- */
static hcl_errnum_t errno_to_errnum (int errcode)
{
	switch (errcode)
	{
		case ENOMEM: return HCL_ESYSMEM;
		case EINVAL: return HCL_EINVAL;

	#if defined(EBUSY)
		case EBUSY: return HCL_EBUSY;
	#endif
		case EACCES: return HCL_EACCES;
	#if defined(EPERM)
		case EPERM: return HCL_EPERM;
	#endif
	#if defined(ENOTDIR)
		case ENOTDIR: return HCL_ENOTDIR;
	#endif
		case ENOENT: return HCL_ENOENT;
	#if defined(EEXIST)
		case EEXIST: return HCL_EEXIST;
	#endif
	#if defined(EINTR)
		case EINTR:  return HCL_EINTR;
	#endif

	#if defined(EPIPE)
		case EPIPE:  return HCL_EPIPE;
	#endif

	#if defined(EAGAIN) && defined(EWOULDBLOCK) && (EAGAIN != EWOULDBLOCK)
		case EAGAIN:
		case EWOULDBLOCK: return HCL_EAGAIN;
	#elif defined(EAGAIN)
		case EAGAIN: return HCL_EAGAIN;
	#elif defined(EWOULDBLOCK)
		case EWOULDBLOCK: return HCL_EAGAIN;
	#endif

	#if defined(EBADF)
		case EBADF: return HCL_EBADHND;
	#endif

	#if defined(EIO)
		case EIO: return HCL_EIOERR;
	#endif

		default: return HCL_ESYSERR;
	}
}

#if defined(_WIN32)
static hcl_errnum_t winerr_to_errnum (DWORD errcode)
{
	switch (errcode)
	{
		case ERROR_NOT_ENOUGH_MEMORY:
		case ERROR_OUTOFMEMORY:
			return HCL_ESYSMEM;

		case ERROR_INVALID_PARAMETER:
		case ERROR_INVALID_NAME:
			return HCL_EINVAL;

		case ERROR_INVALID_HANDLE:
			return HCL_EBADHND;

		case ERROR_ACCESS_DENIED:
		case ERROR_SHARING_VIOLATION:
			return HCL_EACCES;

		case ERROR_FILE_NOT_FOUND:
		case ERROR_PATH_NOT_FOUND:
			return HCL_ENOENT;

		case ERROR_ALREADY_EXISTS:
		case ERROR_FILE_EXISTS:
			return HCL_EEXIST;

		case ERROR_BROKEN_PIPE:
			return HCL_EPIPE;

		default:
			return HCL_ESYSERR;
	}
}
#endif

#if defined(__OS2__)
static hcl_errnum_t os2err_to_errnum (APIRET errcode)
{
	/* APIRET e */
	switch (errcode)
	{
		case ERROR_NOT_ENOUGH_MEMORY:
			return HCL_ESYSMEM;

		case ERROR_INVALID_PARAMETER:
		case ERROR_INVALID_NAME:
			return HCL_EINVAL;

		case ERROR_INVALID_HANDLE:
			return HCL_EBADHND;

		case ERROR_ACCESS_DENIED:
		case ERROR_SHARING_VIOLATION:
			return HCL_EACCES;

		case ERROR_FILE_NOT_FOUND:
		case ERROR_PATH_NOT_FOUND:
			return HCL_ENOENT;

		case ERROR_ALREADY_EXISTS:
			return HCL_EEXIST;

		/*TODO: add more mappings */
		default:
			return HCL_ESYSERR;
	}
}

#if !defined(TCPV40HDRS)
static hcl_errnum_t os2sockerr_to_errnum (int errcode)
{
	switch (errcode)
	{
		case SOCEPERM:  return HCL_EPERM;
		case SOCENOENT: return HCL_ENOENT;
		case SOCEINTR:  return HCL_EINTR;
		case SOCEACCES: return HCL_EACCES;
		case SOCEINVAL: return HCL_EINVAL;
		case SOCENOMEM: return HCL_ESYSMEM;
		case SOCEPIPE:  return HCL_EPIPE;
		default: return HCL_ESYSERR;
	}
}
#endif /* TCPV40HDRS */
#endif /* __OS2__ */

#if defined(macintosh)
static hcl_errnum_t macerr_to_errnum (int errcode)
{
	switch (e)
	{
		case notEnoughMemoryErr:
			return HCL_ESYSMEM;
		case paramErr:
			return HCL_EINVAL;

		case qErr: /* queue element not found during deletion */
		case fnfErr: /* file not found */
		case dirNFErr: /* direcotry not found */
		case resNotFound: /* resource not found */
		case resFNotFound: /* resource file not found */
		case nbpNotFound: /* name not found on remove */
			return HCL_ENOENT;

		/*TODO: add more mappings */
		default:
			return HCL_ESYSERR;
	}
}
#endif

static hcl_errnum_t _syserrstrb (hcl_t* hcl, int syserr_type, int syserr_code, hcl_bch_t* buf, hcl_oow_t len)
{
	switch (syserr_type)
	{
		case 2:
		#if defined(__OS2__)
			#if defined(TCPV40HDRS)
			if (buf) 
			{
				char tmp[64];
				sprintf (tmp, "socket error %d", (int)syserr_code);
				hcl_copy_bcstr (buf, len, tmp);
			}
			return HCL_ESYSERR;
			#else
			/* sock_strerror() available in tcpip32.dll only */
			if (buf) hcl_copy_bcstr (buf, len, sock_strerror(syserr_code));
			return os2sockerr_to_errnum(syserr_code);
			#endif
		#endif
			/* fall thru for other platforms */

		case 1:
		#if defined(_WIN32)
			if (buf)
			{
				DWORD rc;
				rc = FormatMessageA (
					FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL, syserr_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					buf, len, HCL_NULL
				);
				while (rc > 0 && buf[rc - 1] == '\r' || buf[rc - 1] == '\n') buf[--rc] = '\0';
			}
			return winerr_to_errnum(syserr_code);
		#elif defined(__OS2__)
			if (buf)
			{
				char tmp[64];
				sprintf (tmp, "system error %d", (int)syserr_code);
				hcl_copy_bcstr (buf, len, tmp);
			}
			return os2err_to_errnum(syserr_code);
		#elif defined(macintosh)
			/* TODO: convert code to string */
			if (buf) hcl_copy_bcstr (buf, len, "system error");
			return macerr_to_errnum(syserr_code);
		#else
			/* in other systems, errno is still the native system error code.
			 * fall thru */
		#endif

		case 0:
		#if defined(HAVE_STRERROR_R)
			if (buf) strerror_r (syserr_code, buf, len);
		#else
			/* this is not thread safe */
			if (buf) hcl_copy_bcstr (buf, len, strerror(syserr_code));
		#endif
			return errno_to_errnum(syserr_code);
	}

	if (buf) hcl_copy_bcstr (buf, len, "system error");
	return HCL_ESYSERR;
}


/* --------------------------------------------------------------------------
 * ASSERTION SUPPORT
 * -------------------------------------------------------------------------- */

#if defined(HCL_BUILD_RELEASE)

static void _assertfail (hcl_t* hcl, const hcl_bch_t* expr, const hcl_bch_t* file, hcl_oow_t line)
{
	/* do nothing */
}

#else /* defined(HCL_BUILD_RELEASE) */

/* --------------------------------------------------------------------------
 * SYSTEM DEPENDENT HEADERS
 * -------------------------------------------------------------------------- */

#if defined(_WIN32)
#	include <windows.h>
#	include <errno.h>
#elif defined(__OS2__)

#	define INCL_DOSERRORS
#	include <os2.h>
#elif defined(__DOS__)
#	include <dos.h>
#	if defined(_INTELC32_)
#		define DOS_EXIT 0x4C
#	else
#		include <dosfunc.h>
#	endif
#	include <errno.h>
#elif defined(vms) || defined(__vms)
#	define __NEW_STARLET 1
#	include <starlet.h> /* (SYS$...) */
#	include <ssdef.h> /* (SS$...) */
#	include <lib$routines.h> /* (lib$...) */
#elif defined(macintosh)
#	include <MacErrors.h>
#	include <Process.h>
#	include <Dialogs.h>
#	include <TextUtils.h>
#else
#	include <sys/types.h>
#	include <unistd.h>
#	include <signal.h>
#	include <errno.h>
#endif

#if defined(HCL_ENABLE_LIBUNWIND)
#include <libunwind.h>
static void backtrace_stack_frames (hcl_t* hcl)
{
	unw_cursor_t cursor;
	unw_context_t context;
	int n;

	unw_getcontext(&context);
	unw_init_local(&cursor, &context);

	hcl_logbfmt (hcl, HCL_LOG_UNTYPED | HCL_LOG_DEBUG, "[BACKTRACE]\n");
	for (n = 0; unw_step(&cursor) > 0; n++)
	{
		unw_word_t ip, sp, off;
		char symbol[256];

		unw_get_reg (&cursor, UNW_REG_IP, &ip);
		unw_get_reg (&cursor, UNW_REG_SP, &sp);

		if (unw_get_proc_name(&cursor, symbol, HCL_COUNTOF(symbol), &off))
		{
			hcl_copy_bcstr (symbol, HCL_COUNTOF(symbol), "<unknown>");
		}

		hcl_logbfmt (hcl, HCL_LOG_UNTYPED | HCL_LOG_DEBUG,
			"#%02d ip=0x%*p sp=0x%*p %hs+0x%zu\n",
			n, HCL_SIZEOF(void*) * 2, (void*)ip, HCL_SIZEOF(void*) * 2, (void*)sp, symbol, (hcl_oow_t)off);
	}
}
#elif defined(HAVE_BACKTRACE)
#include <execinfo.h>
static void backtrace_stack_frames (hcl_t* hcl)
{
	void* btarray[128];
	hcl_oow_t btsize;
	char** btsyms;

	btsize = backtrace (btarray, HCL_COUNTOF(btarray));
	btsyms = backtrace_symbols (btarray, btsize);
	if (btsyms)
	{
		hcl_oow_t i;
		hcl_logbfmt (hcl, HCL_LOG_UNTYPED | HCL_LOG_DEBUG, "[BACKTRACE]\n");

		for (i = 0; i < btsize; i++)
		{
			hcl_logbfmt (hcl, HCL_LOG_UNTYPED | HCL_LOG_DEBUG, "  %hs\n", btsyms[i]);
		}
		free (btsyms);
	}
}
#else
static void backtrace_stack_frames (hcl_t* hcl)
{
	/* do nothing. not supported */
}
#endif

static void _assertfail (hcl_t* hcl, const hcl_bch_t* expr, const hcl_bch_t* file, hcl_oow_t line)
{
	hcl_logbfmt (hcl, HCL_LOG_UNTYPED | HCL_LOG_FATAL, "ASSERTION FAILURE: %hs at %hs:%zu\n", expr, file, line);
	backtrace_stack_frames (hcl);

#if defined(_WIN32)
	ExitProcess (249);
#elif defined(__OS2__)
	DosExit (EXIT_PROCESS, 249);
#elif defined(__DOS__)
	{
		union REGS regs;
		regs.h.ah = DOS_EXIT;
		regs.h.al = 249;
		intdos (&regs, &regs);
	}
#elif defined(vms) || defined(__vms)
	lib$stop (SS$_ABORT); /* use SS$_OPCCUS instead? */
	/* this won't be reached since lib$stop() terminates the process */
	sys$exit (SS$_ABORT); /* this condition code can be shown with
	                       * 'show symbol $status' from the command-line. */
#elif defined(macintosh)

	ExitToShell ();

#else

	kill (getpid(), SIGABRT);
	_exit (1);
#endif
}

#endif /* defined(HCL_BUILD_RELEASE) */


/* -----------------------------------------------------------------
 * HEAP ALLOCATION
 * ----------------------------------------------------------------- */

static int get_huge_page_size (hcl_t* hcl, hcl_oow_t* page_size)
{
	FILE* fp;
	char buf[256];

	fp = fopen("/proc/meminfo", "r");
	if (!fp) return -1;

	while (!feof(fp))
	{
		if (fgets(buf, sizeof(buf) - 1, fp) == NULL) goto oops;

		if (strncmp(buf, "Hugepagesize: ", 13) == 0)
		{
			unsigned long int tmp;
			tmp = strtoul(&buf[13], NULL, 10);
			if (tmp == HCL_TYPE_MAX(unsigned long int) && errno == ERANGE) goto oops;

			*page_size = tmp * 1024; /* KBytes to Bytes */
			fclose (fp);
			return 0;
		}
	}

oops:
	fclose (fp);
	return -1;
}

static void* alloc_heap (hcl_t* hcl, hcl_oow_t* size)
{
#if defined(HAVE_MMAP) && defined(HAVE_MUNMAP) && defined(MAP_ANONYMOUS)
	/* It's called via hcl_makeheap() when HCL creates a GC heap.
	 * The heap is large in size. I can use a different memory allocation
	 * function instead of an ordinary malloc.
	 * upon failure, it doesn't require to set error information as hcl_makeheap()
	 * set the error number to HCL_EOOMEM. */

#if !defined(MAP_HUGETLB) && (defined(__amd64__) || defined(__x86_64__))
#	define MAP_HUGETLB 0x40000
#endif

	hcl_oow_t* ptr;
	int flags;
	hcl_oow_t req_size, align, aligned_size;

	req_size = HCL_SIZEOF(hcl_oow_t) + *size;
	flags = MAP_PRIVATE | MAP_ANONYMOUS;

	#if defined(MAP_UNINITIALIZED)
	flags |= MAP_UNINITIALIZED;
	#endif

	#if defined(MAP_HUGETLB)
	if (get_huge_page_size(hcl, &align) <= -1) align = 2 * 1024 * 1024; /* default to 2MB */
	if (req_size > align / 2)
	{
		/* if the requested size is large enough, attempt HUGETLB */
		flags |= MAP_HUGETLB;
	}
	else
	{
		align = sysconf(_SC_PAGESIZE);
	}
	#else
	align = sysconf(_SC_PAGESIZE);
	#endif

	aligned_size = HCL_ALIGN_POW2(req_size, align);
	ptr = (hcl_oow_t*)mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, flags, -1, 0);
	#if defined(MAP_HUGETLB)
	if (ptr == MAP_FAILED && (flags & MAP_HUGETLB))
	{
		flags &= ~MAP_HUGETLB;
		align = sysconf(_SC_PAGESIZE);
		aligned_size = HCL_ALIGN_POW2(req_size, align);
		ptr = (hcl_oow_t*)mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, flags, -1, 0);
		if (ptr == MAP_FAILED)
		{
			hcl_seterrwithsyserr (hcl, 0, errno);
			return HCL_NULL;
		}
	}
	#else
	if (ptr == MAP_FAILED)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		return HCL_NULL;
	}
	#endif

	*ptr = aligned_size;
	*size = aligned_size - HCL_SIZEOF(hcl_oow_t);

	return (void*)(ptr + 1);

#else
	return HCL_MMGR_ALLOC(hcl->_mmgr, *size);
#endif
}

static void free_heap (hcl_t* hcl, void* ptr)
{
#if defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
	hcl_oow_t* actual_ptr;
	actual_ptr = (hcl_oow_t*)ptr - 1;
	munmap (actual_ptr, *actual_ptr);
#else
	HCL_MMGR_FREE(hcl->_mmgr, ptr);
#endif
}

/* -----------------------------------------------------------------
 * POSSIBLY MONOTONIC TIME
 * ----------------------------------------------------------------- */

void vm_gettime (hcl_t* hcl, hcl_ntime_t* now)
{
#if defined(_WIN32)

	#if defined(_WIN64) || (defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600))
	hcl_uint64_t bigsec, bigmsec;
	bigmsec = GetTickCount64();
	#else
	xtn_t* xtn = GET_XTN(hcl);
	hcl_uint64_t bigsec, bigmsec;
	DWORD msec;

	msec = GetTickCount(); /* this can sustain for 49.7 days */
	if (msec < xtn->tc_last)
	{
		/* i assume the difference is never bigger than 49.7 days */
		/*diff = (HCL_TYPE_MAX(DWORD) - xtn->tc_last) + 1 + msec;*/
		xtn->tc_overflow++;
		bigmsec = ((hcl_uint64_t)HCL_TYPE_MAX(DWORD) * xtn->tc_overflow) + msec;
	}
	else bigmsec = msec;
	xtn->tc_last = msec;
	#endif

	bigsec = HCL_MSEC_TO_SEC(bigmsec);
	bigmsec -= HCL_SEC_TO_MSEC(bigsec);
	HCL_INIT_NTIME(now, bigsec, HCL_MSEC_TO_NSEC(bigmsec));

#elif defined(__OS2__)
	xtn_t* xtn = GET_XTN(hcl);
	ULONG msec;

	#if (HCL_SIZEOF_UINT64_T > 0)
	hcl_uint64_t bigsec, bigmsec;

/* TODO: use DosTmrQueryTime() and DosTmrQueryFreq()? */
	DosQuerySysInfo (QSV_MS_COUNT, QSV_MS_COUNT, &msec, HCL_SIZEOF(msec)); /* milliseconds */
	/* it must return NO_ERROR */
	if (msec < xtn->tc_last)
	{
		xtn->tc_overflow++;
		bigmsec = ((hcl_uint64_t)HCL_TYPE_MAX(ULONG) * xtn->tc_overflow) + msec;
	}
	else bigmsec = msec;
	xtn->tc_last = msec;

	bigsec = HCL_MSEC_TO_SEC(bigmsec);
	bigmsec -= HCL_SEC_TO_MSEC(bigsec);
	HCL_INIT_NTIME (now, bigsec, HCL_MSEC_TO_NSEC(bigmsec));
	#else
    hcl_uint32_t bigsec, bigmsec;

    DosQuerySysInfo (QSV_MS_COUNT, QSV_MS_COUNT, &msec, HCL_SIZEOF(msec));
	bigsec = HCL_MSEC_TO_SEC(msec);
	bigmsec = msec - HCL_SEC_TO_MSEC(bigsec);
	if (msec < xtn->tc_last)
	{
		ULONG i;
		hcl_uint32_t inc;

		xtn->tc_overflow++;
		inc = HCL_MSEC_TO_SEC(HCL_TYPE_MAX(hcl_uint32_t));
		for (i = 0; i < xtn->tc_overflow; i++)
		{
			ULONG max = HCL_TYPE_MAX(hcl_uint32_t) - bigsec;
			if (max > inc)
			{
				bigsec = HCL_TYPE_MAX(hcl_uint32_t);
				break;
			}
			bigsec += inc;
		}
	}
	HCL_INIT_NTIME (now, bigsec, HCL_MSEC_TO_NSEC(bigmsec));
    #endif

#elif defined(__DOS__) && (defined(_INTELC32_) || defined(__WATCOMC__))
	clock_t c;

/* TODO: handle overflow?? */
	c = clock ();
	now->sec = c / CLOCKS_PER_SEC;
	#if (CLOCKS_PER_SEC == 100)
		now->nsec = HCL_MSEC_TO_NSEC((c % CLOCKS_PER_SEC) * 10);
	#elif (CLOCKS_PER_SEC == 1000)
		now->nsec = HCL_MSEC_TO_NSEC(c % CLOCKS_PER_SEC);
	#elif (CLOCKS_PER_SEC == 1000000L)
		now->nsec = HCL_USEC_TO_NSEC(c % CLOCKS_PER_SEC);
	#elif (CLOCKS_PER_SEC == 1000000000L)
		now->nsec = (c % CLOCKS_PER_SEC);
	#else
	#	error UNSUPPORTED CLOCKS_PER_SEC
	#endif

#elif defined(macintosh)
	UnsignedWide tick;
	hcl_uint64_t tick64;
	Microseconds (&tick);
	tick64 = *(hcl_uint64_t*)&tick;
	HCL_INIT_NTIME (now, HCL_USEC_TO_SEC(tick64), HCL_USEC_TO_NSEC(tick64));
#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
	struct timespec ts;
	clock_gettime (CLOCK_MONOTONIC, &ts);
	HCL_INIT_NTIME(now, ts.tv_sec, ts.tv_nsec);
#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
	struct timespec ts;
	clock_gettime (CLOCK_REALTIME, &ts);
	HCL_INIT_NTIME(now, ts.tv_sec, ts.tv_nsec);
#else
	struct timeval tv;
	gettimeofday (&tv, HCL_NULL);
	HCL_INIT_NTIME(now, tv.tv_sec, HCL_USEC_TO_NSEC(tv.tv_usec));
#endif
}

/* -----------------------------------------------------------------
 * IO MULTIPLEXING
 * ----------------------------------------------------------------- */

static int _add_poll_fd (hcl_t* hcl, int fd, int event_mask)
{
#if defined(USE_DEVPOLL)
	xtn_t* xtn = GET_XTN(hcl);
	struct pollfd ev;

	HCL_ASSERT (hcl, xtn->ep >= 0);
	ev.fd = fd;
	ev.events = event_mask;
	ev.revents = 0;
	if (write(xtn->ep, &ev, HCL_SIZEOF(ev)) != HCL_SIZEOF(ev))
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG2 (hcl, "Cannot add file descriptor %d to devpoll - %hs\n", fd, strerror(errno));
		return -1;
	}

	return 0;

#elif defined(USE_KQUEUE)
	xtn_t* xtn = GET_XTN(hcl);
	struct kevent ev;
	hcl_oow_t rindex, roffset;
	hcl_oow_t rv = 0;

	rindex = (hcl_oow_t)fd / (HCL_BITSOF(hcl_oow_t) >> 1);
	roffset = ((hcl_oow_t)fd << 1) % HCL_BITSOF(hcl_oow_t);

	if (rindex >= xtn->ev.reg.capa)
	{
		hcl_oow_t* tmp;
		hcl_oow_t newcapa;

		HCL_STATIC_ASSERT (HCL_SIZEOF(*tmp) == HCL_SIZEOF(*xtn->ev.reg.ptr));

		newcapa = rindex + 1;
		newcapa = HCL_ALIGN_POW2(newcapa, 16);

		tmp = (hcl_oow_t*)hcl_reallocmem(hcl, xtn->ev.reg.ptr, newcapa * HCL_SIZEOF(*tmp));
		if (!tmp)
		{
			const hcl_ooch_t* oldmsg = hcl_backuperrmsg(hcl);
			hcl_seterrbfmt (hcl, HCL_ESYSERR, "unable to add file descriptor %d to kqueue - %js", fd, oldmsg);
			HCL_DEBUG1 (hcl, "%js", hcl_geterrmsg(hcl));
			return -1;
		}

		HCL_MEMSET (&tmp[xtn->ev.reg.capa], 0, newcapa - xtn->ev.reg.capa);
		xtn->ev.reg.ptr = tmp;
		xtn->ev.reg.capa = newcapa;
	}

	if (event_mask & XPOLLIN)
	{
		/*EV_SET (&ev, fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0);*/
		HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
		ev.ident = fd;
		ev.flags = EV_ADD;
	#if defined(USE_THREAD)
		ev.flags |= EV_CLEAR; /* EV_CLEAR for edge trigger? */
	#endif
		ev.filter = EVFILT_READ;
		if (kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL) == -1)
		{
			hcl_seterrwithsyserr (hcl, 0, errno);
			HCL_DEBUG2 (hcl, "Cannot add file descriptor %d to kqueue for read - %hs\n", fd, strerror(errno));
			return -1;
		}

		rv |= 1;
	}
	if (event_mask & XPOLLOUT)
	{
		/*EV_SET (&ev, fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, 0);*/
		HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
		ev.ident = fd;
		ev.flags = EV_ADD;
	#if defined(USE_THREAD)
		ev.flags |= EV_CLEAR; /* EV_CLEAR for edge trigger? */
	#endif
		ev.filter = EVFILT_WRITE;
		if (kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL) == -1)
		{
			hcl_seterrwithsyserr (hcl, 0, errno);
			HCL_DEBUG2 (hcl, "Cannot add file descriptor %d to kqueue for write - %hs\n", fd, strerror(errno));

			if (event_mask & XPOLLIN)
			{
				HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
				ev.ident = fd;
				ev.flags = EV_DELETE;
				ev.filter = EVFILT_READ;
				kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL);
			}
			return -1;
		}

		rv |= 2;
	}

	HCL_SETBITS (hcl_oow_t, xtn->ev.reg.ptr[rindex], roffset, 2, rv);
	return 0;

#elif defined(USE_EPOLL)
	xtn_t* xtn = GET_XTN(hcl);
	struct epoll_event ev;

	HCL_ASSERT (hcl, xtn->ep >= 0);
	HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
	ev.events = event_mask;
	#if defined(USE_THREAD) && defined(EPOLLET)
	/* epoll_wait may return again if the worker thread consumes events.
	 * switch to level-trigger. */
	/* TODO: verify if EPOLLLET is desired */
	ev.events |= EPOLLET/*  | EPOLLRDHUP | EPOLLHUP */;
	#endif
	/*ev.data.ptr = (void*)event_data;*/
	ev.data.fd = fd;
	if (epoll_ctl(xtn->ep, EPOLL_CTL_ADD, fd, &ev) == -1)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG2 (hcl, "Cannot add file descriptor %d to epoll - %hs\n", fd, strerror(errno));
		return -1;
	}
	return 0;

#elif defined(USE_POLL)
	xtn_t* xtn = GET_XTN(hcl);

	MUTEX_LOCK (&xtn->ev.reg.pmtx);
	if (xtn->ev.reg.len >= xtn->ev.reg.capa)
	{
		struct pollfd* tmp, * tmp2;
		hcl_oow_t newcapa;

		newcapa = HCL_ALIGN_POW2 (xtn->ev.reg.len + 1, 256);
		tmp = (struct pollfd*)hcl_reallocmem(hcl, xtn->ev.reg.ptr, newcapa * HCL_SIZEOF(*tmp));
		tmp2 = (struct pollfd*)hcl_reallocmem(hcl, xtn->ev.buf, newcapa * HCL_SIZEOF(*tmp2));
		if (!tmp || !tmp2)
		{
			HCL_DEBUG2 (hcl, "Cannot add file descriptor %d to poll - %hs\n", fd, strerror(errno));
			MUTEX_UNLOCK (&xtn->ev.reg.pmtx);
			if (tmp) hcl_freemem (hcl, tmp);
			return -1;
		}

		xtn->ev.reg.ptr = tmp;
		xtn->ev.reg.capa = newcapa;

		xtn->ev.buf = tmp2;
	}

	xtn->ev.reg.ptr[xtn->ev.reg.len].fd = fd;
	xtn->ev.reg.ptr[xtn->ev.reg.len].events = event_mask;
	xtn->ev.reg.ptr[xtn->ev.reg.len].revents = 0;
	xtn->ev.reg.len++;
	MUTEX_UNLOCK (&xtn->ev.reg.pmtx);

	return 0;

#elif defined(USE_SELECT)
	xtn_t* xtn = GET_XTN(hcl);

	MUTEX_LOCK (&xtn->ev.reg.smtx);
	if (event_mask & XPOLLIN)
	{
		FD_SET (fd, &xtn->ev.reg.rfds);
		if (fd > xtn->ev.reg.maxfd) xtn->ev.reg.maxfd = fd;
	}
	if (event_mask & XPOLLOUT)
	{
		FD_SET (fd, &xtn->ev.reg.wfds);
		if (fd > xtn->ev.reg.maxfd) xtn->ev.reg.maxfd = fd;
	}
	MUTEX_UNLOCK (&xtn->ev.reg.smtx);

	return 0;

#else

	HCL_DEBUG1 (hcl, "Cannot add file descriptor %d to poll - not implemented\n", fd);
	hcl_seterrnum (hcl, HCL_ENOIMPL);
	return -1;
#endif

}

static int _del_poll_fd (hcl_t* hcl, int fd)
{

#if defined(USE_DEVPOLL)
	xtn_t* xtn = GET_XTN(hcl);
	struct pollfd ev;

	HCL_ASSERT (hcl, xtn->ep >= 0);
	ev.fd = fd;
	ev.events = POLLREMOVE;
	ev.revents = 0;
	if (write(xtn->ep, &ev, HCL_SIZEOF(ev)) != HCL_SIZEOF(ev))
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG2 (hcl, "Cannot remove file descriptor %d from devpoll - %hs\n", fd, strerror(errno));
		return -1;
	}

	return 0;

#elif defined(USE_KQUEUE)
	xtn_t* xtn = GET_XTN(hcl);
	hcl_oow_t rindex, roffset;
	int rv;
	struct kevent ev;

	rindex = (hcl_oow_t)fd / (HCL_BITSOF(hcl_oow_t) >> 1);
	roffset = ((hcl_oow_t)fd << 1) % HCL_BITSOF(hcl_oow_t);

	if (rindex >= xtn->ev.reg.capa)
	{
		hcl_seterrbfmt (hcl, HCL_EINVAL, "unknown file descriptor %d", fd);
		HCL_DEBUG2 (hcl, "Cannot remove file descriptor %d from kqueue - %js\n", fd, hcl_geterrmsg(hcl));
		return -1;
	};

	rv = HCL_GETBITS (hcl_oow_t, xtn->ev.reg.ptr[rindex], roffset, 2);

	if (rv & 1)
	{
		/*EV_SET (&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, 0);*/
		HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
		ev.ident = fd;
		ev.flags = EV_DELETE;
		ev.filter = EVFILT_READ;
		kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL);
		/* no error check for now */
	}

	if (rv & 2)
	{
		/*EV_SET (&ev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);*/
		HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
		ev.ident = fd;
		ev.flags = EV_DELETE;
		ev.filter = EVFILT_WRITE;
		kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL);
		/* no error check for now */
	}

	HCL_SETBITS (hcl_oow_t, xtn->ev.reg.ptr[rindex], roffset, 2, 0);
	return 0;

#elif defined(USE_EPOLL)
	xtn_t* xtn = GET_XTN(hcl);
	struct epoll_event ev;

	HCL_ASSERT (hcl, xtn->ep >= 0);
	HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
	if (epoll_ctl(xtn->ep, EPOLL_CTL_DEL, fd, &ev) == -1)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG2 (hcl, "Cannot remove file descriptor %d from epoll - %hs\n", fd, strerror(errno));
		return -1;
	}
	return 0;

#elif defined(USE_POLL)
	xtn_t* xtn = GET_XTN(hcl);
	hcl_oow_t i;

	/* TODO: performance boost. no linear search */
	MUTEX_LOCK (&xtn->ev.reg.pmtx);
	for (i = 0; i < xtn->ev.reg.len; i++)
	{
		if (xtn->ev.reg.ptr[i].fd == fd)
		{
			xtn->ev.reg.len--;
			HCL_MEMMOVE (&xtn->ev.reg.ptr[i], &xtn->ev.reg.ptr[i+1], (xtn->ev.reg.len - i) * HCL_SIZEOF(*xtn->ev.reg.ptr));
			MUTEX_UNLOCK (&xtn->ev.reg.pmtx);
			return 0;
		}
	}
	MUTEX_UNLOCK (&xtn->ev.reg.pmtx);


	HCL_DEBUG1 (hcl, "Cannot remove file descriptor %d from poll - not found\n", fd);
	hcl_seterrnum (hcl, HCL_ENOENT);
	return -1;

#elif defined(USE_SELECT)
	xtn_t* xtn = GET_XTN(hcl);

	MUTEX_LOCK (&xtn->ev.reg.smtx);
	FD_CLR (fd, &xtn->ev.reg.rfds);
	FD_CLR (fd, &xtn->ev.reg.wfds);
	if (fd >= xtn->ev.reg.maxfd)
	{
		int i;
		/* TODO: any way to make this search faster or to do without the search like this */
		for (i = fd - 1; i >= 0; i--)
		{
			if (FD_ISSET(i, &xtn->ev.reg.rfds) || FD_ISSET(i, &xtn->ev.reg.wfds)) break;
		}
		xtn->ev.reg.maxfd = i;
	}
	MUTEX_UNLOCK (&xtn->ev.reg.smtx);

	return 0;

#else

	HCL_DEBUG1 (hcl, "Cannot remove file descriptor %d from poll - not implemented\n", fd);
	hcl_seterrnum (hcl, HCL_ENOIMPL);
	return -1;
#endif
}


static int _mod_poll_fd (hcl_t* hcl, int fd, int event_mask)
{
#if defined(USE_DEVPOLL)

	if (_del_poll_fd (hcl, fd) <= -1) return -1;

	if (_add_poll_fd (hcl, fd, event_mask) <= -1)
	{
		/* TODO: any good way to rollback successful deletion? */
		return -1;
	}

	return 0;
#elif defined(USE_KQUEUE)
	xtn_t* xtn = GET_XTN(hcl);
	hcl_oow_t rindex, roffset;
	int rv, newrv = 0;
	struct kevent ev;

	rindex = (hcl_oow_t)fd / (HCL_BITSOF(hcl_oow_t) >> 1);
	roffset = ((hcl_oow_t)fd << 1) % HCL_BITSOF(hcl_oow_t);

	if (rindex >= xtn->ev.reg.capa)
	{
		hcl_seterrbfmt (hcl, HCL_EINVAL, "unknown file descriptor %d", fd);
		HCL_DEBUG2 (hcl, "Cannot modify file descriptor %d in kqueue - %js\n", fd, hcl_geterrmsg(hcl));
		return -1;
	};

	rv = HCL_GETBITS(hcl_oow_t, xtn->ev.reg.ptr[rindex], roffset, 2);

	if (rv & 1)
	{
		if (!(event_mask & XPOLLIN))
		{
			HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
			ev.ident = fd;
			ev.flags = EV_DELETE;
			ev.filter = EVFILT_READ;
			if (kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL) == -1) goto kqueue_syserr;

			newrv &= ~1;
		}
	}
	else
	{
		if (event_mask & XPOLLIN)
		{
			HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
			ev.ident = fd;
			ev.flags = EV_ADD;
		#if defined(USE_THREAD)
			ev.flags |= EV_CLEAR; /* EV_CLEAR for edge trigger? */
		#endif
			ev.filter = EVFILT_READ;
			if (kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL) == -1) goto kqueue_syserr;

			newrv |= 1;
		}
	}

	if (rv & 2)
	{
		if (!(event_mask & XPOLLOUT))
		{
			HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
			ev.ident = fd;
			ev.flags = EV_DELETE;
			ev.filter = EVFILT_WRITE;
			/* there is no operation rollback for the (rv & 1) case.
			 * the rollback action may fail again even if i try it */
			if (kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL) == -1) goto kqueue_syserr;

			newrv &= ~2;
		}
	}
	else
	{
		if (event_mask & XPOLLOUT)
		{
			HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
			ev.ident = fd;
			ev.flags = EV_ADD;
		#if defined(USE_THREAD)
			ev.flags |= EV_CLEAR; /* EV_CLEAR for edge trigger? */
		#endif
			ev.filter = EVFILT_WRITE;

			/* there is no operation rollback for the (rv & 1) case.
			 * the rollback action may fail again even if i try it */
			if (kevent(xtn->ep, &ev, 1, HCL_NULL, 0, HCL_NULL) == -1) goto kqueue_syserr;

			newrv |= 2;
		}
	}

	HCL_SETBITS (hcl_oow_t, xtn->ev.reg.ptr[rindex], roffset, 2, newrv);
	return 0;

kqueue_syserr:
	hcl_seterrwithsyserr (hcl, 0, errno);
	HCL_DEBUG2 (hcl, "Cannot modify file descriptor %d in kqueue - %hs\n", fd, strerror(errno));
	return -1;

#elif defined(USE_EPOLL)
	xtn_t* xtn = GET_XTN(hcl);
	struct epoll_event ev;

	HCL_ASSERT (hcl, xtn->ep >= 0);
	HCL_MEMSET (&ev, 0, HCL_SIZEOF(ev));
	ev.events = event_mask;
	#if defined(USE_THREAD) && defined(EPOLLET)
	/* epoll_wait may return again if the worker thread consumes events.
	 * switch to level-trigger. */
	/* TODO: verify if EPOLLLET is desired */
	ev.events |= EPOLLET;
	#endif
	ev.data.fd = fd;
	if (epoll_ctl(xtn->ep, EPOLL_CTL_MOD, fd, &ev) == -1)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG2 (hcl, "Cannot modify file descriptor %d in epoll - %hs\n", fd, strerror(errno));
		return -1;
	}

	return 0;

#elif defined(USE_POLL)

	xtn_t* xtn = GET_XTN(hcl);
	hcl_oow_t i;

	MUTEX_LOCK (&xtn->ev.reg.pmtx);
	for (i = 0; i < xtn->ev.reg.len; i++)
	{
		if (xtn->ev.reg.ptr[i].fd == fd)
		{
			HCL_MEMMOVE (&xtn->ev.reg.ptr[i], &xtn->ev.reg.ptr[i+1], (xtn->ev.reg.len - i - 1) * HCL_SIZEOF(*xtn->ev.reg.ptr));
			xtn->ev.reg.ptr[i].fd = fd;
			xtn->ev.reg.ptr[i].events = event_mask;
			xtn->ev.reg.ptr[i].revents = 0;
			MUTEX_UNLOCK (&xtn->ev.reg.pmtx);

			return 0;
		}
	}
	MUTEX_UNLOCK (&xtn->ev.reg.pmtx);

	HCL_DEBUG1 (hcl, "Cannot modify file descriptor %d in poll - not found\n", fd);
	hcl_seterrnum (hcl, HCL_ENOENT);
	return -1;

#elif defined(USE_SELECT)

	xtn_t* xtn = GET_XTN(hcl);

	MUTEX_LOCK (&xtn->ev.reg.smtx);
	HCL_ASSERT (hcl, fd <= xtn->ev.reg.maxfd);

	if (event_mask & XPOLLIN)
		FD_SET (fd, &xtn->ev.reg.rfds);
	else
		FD_CLR (fd, &xtn->ev.reg.rfds);

	if (event_mask & XPOLLOUT)
		FD_SET (fd, &xtn->ev.reg.wfds);
	else
		FD_CLR (fd, &xtn->ev.reg.wfds);
	MUTEX_UNLOCK (&xtn->ev.reg.smtx);

	return 0;

#else
	HCL_DEBUG1 (hcl, "Cannot modify file descriptor %d in poll - not implemented\n", fd);
	hcl_seterrnum (hcl, HCL_ENOIMPL);
	return -1;
#endif
}

static int vm_muxadd (hcl_t* hcl, hcl_ooi_t io_handle, hcl_ooi_t mask)
{
	int event_mask;

	event_mask = 0;
	if (mask & HCL_SEMAPHORE_IO_MASK_INPUT) event_mask |= XPOLLIN;
	if (mask & HCL_SEMAPHORE_IO_MASK_OUTPUT) event_mask |= XPOLLOUT;

	if (event_mask == 0)
	{
		HCL_DEBUG2 (hcl, "<vm_muxadd> Invalid semaphore mask %zd on handle %zd\n", mask, io_handle);
		hcl_seterrbfmt (hcl, HCL_EINVAL, "invalid semaphore mask %zd on handle %zd", mask, io_handle);
		return -1;
	}

	return _add_poll_fd(hcl, io_handle, event_mask);
}

static int vm_muxmod (hcl_t* hcl, hcl_ooi_t io_handle, hcl_ooi_t mask)
{
	int event_mask;

	event_mask = 0;
	if (mask & HCL_SEMAPHORE_IO_MASK_INPUT) event_mask |= XPOLLIN;
	if (mask & HCL_SEMAPHORE_IO_MASK_OUTPUT) event_mask |= XPOLLOUT;

	if (event_mask == 0)
	{
		HCL_DEBUG2 (hcl, "<vm_muxadd> Invalid semaphore mask %zd on handle %zd\n", mask, io_handle);
		hcl_seterrbfmt (hcl, HCL_EINVAL, "invalid semaphore mask %zd on handle %zd", mask, io_handle);
		return -1;
	}

	return _mod_poll_fd(hcl, io_handle, event_mask);
}

static int vm_muxdel (hcl_t* hcl, hcl_ooi_t io_handle)
{
	return _del_poll_fd(hcl, io_handle);
}

#if defined(USE_THREAD)
static void* iothr_main (void* arg)
{
	hcl_t* hcl = (hcl_t*)arg;
	xtn_t* xtn = GET_XTN(hcl);

	/*while (!hcl->abort_req)*/
	while (!xtn->iothr.abort)
	{
		if (xtn->ev.len <= 0) /* TODO: no mutex needed for this check? */
		{
			int n;
		#if defined(USE_DEVPOLL)
			struct dvpoll dvp;
		#elif defined(USE_KQUEUE)
			struct timespec ts;
		#elif defined(USE_POLL)
			hcl_oow_t nfds;
		#elif defined(USE_SELECT)
			struct timeval tv;
			fd_set rfds;
			fd_set wfds;
			int maxfd;
		#endif

		poll_for_event:

		#if defined(USE_DEVPOLL)
			dvp.dp_timeout = 10000; /* milliseconds */
			dvp.dp_fds = xtn->ev.buf;
			dvp.dp_nfds = HCL_COUNTOF(xtn->ev.buf);
			n = ioctl (xtn->ep, DP_POLL, &dvp);
		#elif defined(USE_KQUEUE)
			ts.tv_sec = 10;
			ts.tv_nsec = 0;
			n = kevent(xtn->ep, HCL_NULL, 0, xtn->ev.buf, HCL_COUNTOF(xtn->ev.buf), &ts);
			/* n == 0: timeout
			 * n == -1: error */
		#elif defined(USE_EPOLL)
			n = epoll_wait(xtn->ep, xtn->ev.buf, HCL_COUNTOF(xtn->ev.buf), 10000); /* TODO: make this timeout value in the io thread */
		#elif defined(USE_POLL)
			MUTEX_LOCK (&xtn->ev.reg.pmtx);
			HCL_MEMCPY (xtn->ev.buf, xtn->ev.reg.ptr, xtn->ev.reg.len * HCL_SIZEOF(*xtn->ev.buf));
			nfds = xtn->ev.reg.len;
			MUTEX_UNLOCK (&xtn->ev.reg.pmtx);
			n = poll(xtn->ev.buf, nfds, 10000);
			if (n > 0)
			{
				/* compact the return buffer as poll() doesn't */
				hcl_oow_t i, j;
				for (i = 0, j = 0; i < nfds && j < n; i++)
				{
					if (xtn->ev.buf[i].revents)
					{
						if (j != i) xtn->ev.buf[j] = xtn->ev.buf[i];
						j++;
					}
				}
				n = j;
			}
		#elif defined(USE_SELECT)
			tv.tv_sec = 10;
			tv.tv_usec = 0;
			MUTEX_LOCK (&xtn->ev.reg.smtx);
			maxfd = xtn->ev.reg.maxfd;
			HCL_MEMCPY (&rfds, &xtn->ev.reg.rfds, HCL_SIZEOF(rfds));
			HCL_MEMCPY (&wfds, &xtn->ev.reg.wfds, HCL_SIZEOF(wfds));
			MUTEX_UNLOCK (&xtn->ev.reg.smtx);
			n = select (maxfd + 1, &rfds, &wfds, HCL_NULL, &tv);
			if (n > 0)
			{
				int fd, count = 0;
				for (fd = 0;  fd <= maxfd; fd++)
				{
					int events = 0;
					if (FD_ISSET(fd, &rfds)) events |= XPOLLIN;
					if (FD_ISSET(fd, &wfds)) events |= XPOLLOUT;

					if (events)
					{
						HCL_ASSERT (hcl, count < HCL_COUNTOF(xtn->ev.buf));
						xtn->ev.buf[count].fd = fd;
						xtn->ev.buf[count].events = events;
						count++;
					}
				}

				n = count;
				HCL_ASSERT (hcl, n > 0);
			}
		#endif

			pthread_mutex_lock (&xtn->ev.mtx);
			if (n <= -1)
			{
				/* TODO: don't use HCL_DEBUG2. it's not thread safe... */
				/* the following call has a race-condition issue when called in this separate thread */
				/*HCL_DEBUG2 (hcl, "Warning: multiplexer wait failure - %d, %hs\n", errno, strerror(errno));*/
			}
			else if (n > 0)
			{
				xtn->ev.len = n;
			}
			pthread_cond_signal (&xtn->ev.cnd2);
			pthread_mutex_unlock (&xtn->ev.mtx);
		}
		else
		{
			/* the event buffer has not been emptied yet */
			struct timespec ts;

			pthread_mutex_lock (&xtn->ev.mtx);
			if (xtn->ev.len <= 0)
			{
				/* it got emptied between the if check and pthread_mutex_lock() above */
				pthread_mutex_unlock (&xtn->ev.mtx);
				goto poll_for_event;
			}

		#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
			clock_gettime (CLOCK_REALTIME, &ts);
		#else
			{
				struct timeval tv;
				gettimeofday (&tv, HCL_NULL);
				ts.tv_sec = tv.tv_sec;
				ts.tv_nsec = HCL_USEC_TO_NSEC(tv.tv_usec);
			}
		#endif
			ts.tv_sec += 10;
			pthread_cond_timedwait (&xtn->ev.cnd, &xtn->ev.mtx, &ts);
			pthread_mutex_unlock (&xtn->ev.mtx);
		}

		/*sched_yield ();*/
	}

	return HCL_NULL;
}
#endif

static void vm_muxwait (hcl_t* hcl, const hcl_ntime_t* dur, hcl_vmprim_muxwait_cb_t muxwcb)
{
	xtn_t* xtn = GET_XTN(hcl);

#if defined(USE_THREAD)
	int n;

	/* create a thread if mux wait is started at least once. */
	if (!xtn->iothr.up)
	{
		xtn->iothr.up = 1;
		if (pthread_create(&xtn->iothr.thr, HCL_NULL, iothr_main, hcl) != 0)
		{
			HCL_LOG2 (hcl, HCL_LOG_WARN, "Warning: pthread_create failure - %d, %hs\n", errno, strerror(errno));
			xtn->iothr.up = 0;
/* TODO: switch to the non-threaded mode? */
		}
	}

	if (xtn->iothr.abort) return;

	if (xtn->ev.len <= 0)
	{
		struct timespec ts;
		hcl_ntime_t ns;

		if (!dur) return; /* immediate check is requested. and there is no event */

	#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
		clock_gettime (CLOCK_REALTIME, &ts);
		ns.sec = ts.tv_sec;
		ns.nsec = ts.tv_nsec;
	#else
		{
			struct timeval tv;
			gettimeofday (&tv, HCL_NULL);
			ns.sec = tv.tv_sec;
			ns.nsec = HCL_USEC_TO_NSEC(tv.tv_usec);
		}
	#endif
		HCL_ADD_NTIME (&ns, &ns, dur);
		ts.tv_sec = ns.sec;
		ts.tv_nsec = ns.nsec;

		pthread_mutex_lock (&xtn->ev.mtx);
		if (xtn->ev.len <= 0)
		{
			/* the event buffer is still empty */
			pthread_cond_timedwait (&xtn->ev.cnd2, &xtn->ev.mtx, &ts);
		}
		pthread_mutex_unlock (&xtn->ev.mtx);
	}

	n = xtn->ev.len;

	if (n > 0)
	{
		do
		{
			--n;

		#if defined(USE_DEVPOLL)
			if (xtn->ev.buf[n].fd == xtn->iothr.p[0])
		#elif defined(USE_KQUEUE)
			if (xtn->ev.buf[n].ident == xtn->iothr.p[0])
		#elif defined(USE_EPOLL)
			/*if (xtn->ev.buf[n].data.ptr == (void*)HCL_TYPE_MAX(hcl_oow_t))*/
			if (xtn->ev.buf[n].data.fd == xtn->iothr.p[0])
		#elif defined(USE_POLL)
			if (xtn->ev.buf[n].fd == xtn->iothr.p[0])
		#elif defined(USE_SELECT)
			if (xtn->ev.buf[n].fd == xtn->iothr.p[0])
		#else
		#	error UNSUPPORTED
		#endif
			{
				hcl_uint8_t u8;
				while (read(xtn->iothr.p[0], &u8, HCL_SIZEOF(u8)) > 0)
				{
					/* consume as much as possible */;
					if (u8 == 'Q') xtn->iothr.abort = 1;
				}
			}
			else if (muxwcb)
			{
				int revents;
				hcl_ooi_t mask;

			#if defined(USE_DEVPOLL)
				revents = xtn->ev.buf[n].revents;
			#elif defined(USE_KQUEUE)
				if (xtn->ev.buf[n].filter == EVFILT_READ) mask = HCL_SEMAPHORE_IO_MASK_INPUT;
				else if (xtn->ev.buf[n].filter == EVFILT_WRITE) mask = HCL_SEMAPHORE_IO_MASK_OUTPUT;
				else mask = 0;
				goto call_muxwcb_kqueue;
			#elif defined(USE_EPOLL)
				revents = xtn->ev.buf[n].events;
			#elif defined(USE_POLL)
				revents = xtn->ev.buf[n].revents;
			#elif defined(USE_SELECT)
				revents = xtn->ev.buf[n].events;
			#endif

				mask = 0;
				if (revents & XPOLLIN) mask |= HCL_SEMAPHORE_IO_MASK_INPUT;
				if (revents & XPOLLOUT) mask |= HCL_SEMAPHORE_IO_MASK_OUTPUT;
				if (revents & XPOLLERR) mask |= HCL_SEMAPHORE_IO_MASK_ERROR;
				if (revents & XPOLLHUP) mask |= HCL_SEMAPHORE_IO_MASK_HANGUP;

			#if defined(USE_DEVPOLL)
				muxwcb (hcl, xtn->ev.buf[n].fd, mask);
			#elif defined(USE_KQUEUE)
			call_muxwcb_kqueue:
				muxwcb (hcl, xtn->ev.buf[n].ident, mask);
			#elif defined(USE_EPOLL)
				muxwcb (hcl, xtn->ev.buf[n].data.fd, mask);
			#elif defined(USE_POLL)
				muxwcb (hcl, xtn->ev.buf[n].fd, mask);
			#elif defined(USE_SELECT)
				muxwcb (hcl, xtn->ev.buf[n].fd, mask);
			#else
			#	error UNSUPPORTED
			#endif
			}
		}
		while (n > 0);

		pthread_mutex_lock (&xtn->ev.mtx);
		xtn->ev.len = 0;
		pthread_cond_signal (&xtn->ev.cnd);
		pthread_mutex_unlock (&xtn->ev.mtx);
	}

#else /* USE_THREAD */
	int n;
	#if defined(USE_DEVPOLL)
	int tmout;
	struct dvpoll dvp;
	#elif defined(USE_KQUEUE)
	struct timespec ts;
	#elif defined(USE_EPOLL)
	int tmout;
	#elif defined(USE_POLL)
	int tmout;
	#elif defined(USE_SELECT)
	struct timeval tv;
	fd_set rfds, wfds;
	int maxfd;
	#endif


	#if defined(USE_DEVPOLL)
	tmout = dur? HCL_SECNSEC_TO_MSEC(dur->sec, dur->nsec): 0;

	dvp.dp_timeout = tmout; /* milliseconds */
	dvp.dp_fds = xtn->ev.buf;
	dvp.dp_nfds = HCL_COUNTOF(xtn->ev.buf);
	n = ioctl(xtn->ep, DP_POLL, &dvp);

	#elif defined(USE_KQUEUE)

	if (dur)
	{
		ts.tv_sec = dur->sec;
		ts.tv_nsec = dur->nsec;
	}
	else
	{
		ts.tv_sec = 0;
		ts.tv_nsec = 0;
	}

	n = kevent(xtn->ep, HCL_NULL, 0, xtn->ev.buf, HCL_COUNTOF(xtn->ev.buf), &ts);
	/* n == 0: timeout
	 * n == -1: error */

	#elif defined(USE_EPOLL)
	tmout = dur? HCL_SECNSEC_TO_MSEC(dur->sec, dur->nsec): 0;
	n = epoll_wait(xtn->ep, xtn->ev.buf, HCL_COUNTOF(xtn->ev.buf), tmout);

	#elif defined(USE_POLL)
	tmout = dur? HCL_SECNSEC_TO_MSEC(dur->sec, dur->nsec): 0;
	HCL_MEMCPY (xtn->ev.buf, xtn->ev.reg.ptr, xtn->ev.reg.len * HCL_SIZEOF(*xtn->ev.buf));
	n = poll(xtn->ev.buf, xtn->ev.reg.len, tmout);
	if (n > 0)
	{
		/* compact the return buffer as poll() doesn't */
		hcl_oow_t i, j;
		for (i = 0, j = 0; i < xtn->ev.reg.len && j < n; i++)
		{
			if (xtn->ev.buf[i].revents)
			{
				if (j != i) xtn->ev.buf[j] = xtn->ev.buf[i];
				j++;
			}
		}
		n = j;
	}
	#elif defined(USE_SELECT)
	if (dur)
	{
		tv.tv_sec = dur->sec;
		tv.tv_usec = HCL_NSEC_TO_USEC(dur->nsec);
	}
	else
	{
		tv.tv_sec = 0;
		tv.tv_usec = 0;
	}
	maxfd = xtn->ev.reg.maxfd;
	HCL_MEMCPY (&rfds, &xtn->ev.reg.rfds, HCL_SIZEOF(rfds));
	HCL_MEMCPY (&wfds, &xtn->ev.reg.wfds, HCL_SIZEOF(wfds));
	n = select(maxfd + 1, &rfds, &wfds, HCL_NULL, &tv);
	if (n > 0)
	{
		int fd, count = 0;
		for (fd = 0; fd <= maxfd; fd++)
		{
			int events = 0;
			if (FD_ISSET(fd, &rfds)) events |= XPOLLIN;
			if (FD_ISSET(fd, &wfds)) events |= XPOLLOUT;

			if (events)
			{
				HCL_ASSERT (hcl, count < HCL_COUNTOF(xtn->ev.buf));
				xtn->ev.buf[count].fd = fd;
				xtn->ev.buf[count].events = events;
				count++;
			}
		}

		n = count;
		HCL_ASSERT (hcl, n > 0);
	}
	#endif

	if (n <= -1)
	{
	#if defined(__OS2__)
		hcl_seterrwithsyserr (hcl, 2, sock_errno());
		HCL_DEBUG2 (hcl, "Warning: multiplexer wait failure - %d, %js\n", sock_errno(), hcl_geterrmsg(hcl));
	#else
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG2 (hcl, "Warning: multiplexer wait failure - %d, %js\n", errno, hcl_geterrmsg(hcl));
	#endif
	}
	else
	{
		xtn->ev.len = n;
	}

	/* the muxwcb must be valid all the time in a non-threaded mode */
	HCL_ASSERT (hcl, muxwcb != HCL_NULL);

	while (n > 0)
	{
		int revents;
		hcl_ooi_t mask;

		--n;

	#if defined(USE_DEVPOLL)
		revents = xtn->ev.buf[n].revents;
	#elif defined(USE_KQUEUE)
		if (xtn->ev.buf[n].filter == EVFILT_READ) mask = HCL_SEMAPHORE_IO_MASK_INPUT;
		else if (xtn->ev.buf[n].filter == EVFILT_WRITE) mask = HCL_SEMAPHORE_IO_MASK_OUTPUT;
		else mask = 0;
		goto call_muxwcb_kqueue;
	#elif defined(USE_EPOLL)
		revents = xtn->ev.buf[n].events;
	#elif defined(USE_POLL)
		revents = xtn->ev.buf[n].revents;
	#elif defined(USE_SELECT)
		revents = xtn->ev.buf[n].events;
	#else
		revents = 0; /* TODO: fake. unsupported but to compile on such an unsupported system.*/
	#endif

		mask = 0;
		if (revents & XPOLLIN) mask |= HCL_SEMAPHORE_IO_MASK_INPUT;
		if (revents & XPOLLOUT) mask |= HCL_SEMAPHORE_IO_MASK_OUTPUT;
		if (revents & XPOLLERR) mask |= HCL_SEMAPHORE_IO_MASK_ERROR;
		if (revents & XPOLLHUP) mask |= HCL_SEMAPHORE_IO_MASK_HANGUP;

	#if defined(USE_DEVPOLL)
		muxwcb (hcl, xtn->ev.buf[n].fd, mask);
	#elif defined(USE_KQUEUE)
	call_muxwcb_kqueue:
		muxwcb (hcl, xtn->ev.buf[n].ident, mask);
	#elif defined(USE_EPOLL)
		muxwcb (hcl, xtn->ev.buf[n].data.fd, mask);
	#elif defined(USE_POLL)
		muxwcb (hcl, xtn->ev.buf[n].fd, mask);
	#elif defined(USE_SELECT)
		muxwcb (hcl, xtn->ev.buf[n].fd, mask);
	#endif
	}

	xtn->ev.len = 0;
#endif  /* USE_THREAD */
}

/* -----------------------------------------------------------------
 * SLEEPING
 * ----------------------------------------------------------------- */

#if defined(__DOS__)
#	if defined(_INTELC32_)
	void _halt_cpu (void);
#	elif defined(__WATCOMC__)
	void _halt_cpu (void);
#	pragma aux _halt_cpu = "hlt"
#	endif
#endif

static int vm_sleep (hcl_t* hcl, const hcl_ntime_t* dur)
{
#if defined(_WIN32)
	xtn_t* xtn = GET_XTN(hcl);
	if (xtn->waitable_timer)
	{
		LARGE_INTEGER li;
		li.QuadPart = -(HCL_SECNSEC_TO_NSEC(dur->sec, dur->nsec) / 100); /* in 100 nanoseconds */
		if(SetWaitableTimer(xtn->waitable_timer, &li, 0, HCL_NULL, HCL_NULL, FALSE) == FALSE) goto normal_sleep;
		WaitForSingleObject(xtn->waitable_timer, INFINITE);
	}
	else
	{
	normal_sleep:
		/* fallback to normal Sleep() */
		Sleep (HCL_SECNSEC_TO_MSEC(dur->sec,dur->nsec));
	}
#elif defined(__OS2__)

	/* TODO: in gui mode, this is not a desirable method???
	 *       this must be made event-driven coupled with the main event loop */
	DosSleep (HCL_SECNSEC_TO_MSEC(dur->sec,dur->nsec));

#elif defined(macintosh)

	/* TODO: ... */

#elif defined(__DOS__) && (defined(_INTELC32_) || defined(__WATCOMC__))

	clock_t c;

	c = clock ();
	c += dur->sec * CLOCKS_PER_SEC;

	#if (CLOCKS_PER_SEC == 100)
		c += HCL_NSEC_TO_MSEC(dur->nsec) / 10;
	#elif (CLOCKS_PER_SEC == 1000)
		c += HCL_NSEC_TO_MSEC(dur->nsec);
	#elif (CLOCKS_PER_SEC == 1000000L)
		c += HCL_NSEC_TO_USEC(dur->nsec);
	#elif (CLOCKS_PER_SEC == 1000000000L)
		c += dur->nsec;
	#else
	#	error UNSUPPORTED CLOCKS_PER_SEC
	#endif

/* TODO: handle clock overvlow */
/* TODO: check if there is abortion request or interrupt */
	while (c > clock())
	{
		_halt_cpu();
	}

#else
	#if defined(HAVE_NANOSLEEP)
		struct timespec ts;
		ts.tv_sec = dur->sec;
		ts.tv_nsec = dur->nsec;
		nanosleep (&ts, HCL_NULL);
	#elif defined(HAVE_USLEEP)
		usleep (HCL_SECNSEC_TO_USEC(dur->sec, dur->nsec));
	#else
	#	error UNSUPPORT SLEEP
	#endif
#endif

	return 0;
}

/* -----------------------------------------------------------------
 * SHARED LIBRARY HANDLING
 * ----------------------------------------------------------------- */

#if defined(USE_LTDL)
#	define sys_dl_error() lt_dlerror()
#	define sys_dl_open(x) lt_dlopen(x)
#	define sys_dl_openext(x) lt_dlopenext(x)
#	define sys_dl_close(x) lt_dlclose(x)
#	define sys_dl_getsym(x,n) lt_dlsym(x,n)

#elif defined(USE_DLFCN)
#	define sys_dl_error() dlerror()
#	define sys_dl_open(x) dlopen(x,RTLD_NOW)
#	define sys_dl_openext(x) dlopen(x,RTLD_NOW)
#	define sys_dl_close(x) dlclose(x)
#	define sys_dl_getsym(x,n) dlsym(x,n)

#elif defined(USE_WIN_DLL)
#	define sys_dl_error() win_dlerror()
#	define sys_dl_open(x) LoadLibraryExA(x, HCL_NULL, 0)
#	define sys_dl_openext(x) LoadLibraryExA(x, HCL_NULL, 0)
#	define sys_dl_close(x) FreeLibrary(x)
#	define sys_dl_getsym(x,n) GetProcAddress(x,n)

#elif defined(USE_MACH_O_DYLD)
#	define sys_dl_error() mach_dlerror()
#	define sys_dl_open(x) mach_dlopen(x)
#	define sys_dl_openext(x) mach_dlopen(x)
#	define sys_dl_close(x) mach_dlclose(x)
#	define sys_dl_getsym(x,n) mach_dlsym(x,n)
#endif

#if defined(USE_WIN_DLL)

static const char* win_dlerror (void)
{
	/* TODO: handle wchar_t, hcl_ooch_t etc? */
	static char buf[256];
	DWORD rc;

	rc = FormatMessageA (
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		buf, HCL_COUNTOF(buf), HCL_NULL
	);
	while (rc > 0 && buf[rc - 1] == '\r' || buf[rc - 1] == '\n')
	{
		buf[--rc] = '\0';
	}
	return buf;
}

#elif defined(USE_MACH_O_DYLD)
static const char* mach_dlerror_str = "";

static void* mach_dlopen (const char* path)
{
	NSObjectFileImage image;
	NSObjectFileImageReturnCode rc;
	void* handle;

	mach_dlerror_str = "";
	if ((rc = NSCreateObjectFileImageFromFile(path, &image)) != NSObjectFileImageSuccess)
	{
		switch (rc)
		{
			case NSObjectFileImageFailure:
			case NSObjectFileImageFormat:
				mach_dlerror_str = "unable to crate object file image";
				break;

			case NSObjectFileImageInappropriateFile:
				mach_dlerror_str = "inappropriate file";
				break;

			case NSObjectFileImageArch:
				mach_dlerror_str = "incompatible architecture";
				break;

			case NSObjectFileImageAccess:
				mach_dlerror_str = "inaccessible file";
				break;

			default:
				mach_dlerror_str = "unknown error";
				break;
		}
		return HCL_NULL;
	}
	handle = (void*)NSLinkModule(image, path, NSLINKMODULE_OPTION_PRIVATE | NSLINKMODULE_OPTION_RETURN_ON_ERROR);
	NSDestroyObjectFileImage (image);
	return handle;
}

static HCL_INLINE void mach_dlclose (void* handle)
{
	mach_dlerror_str = "";
	NSUnLinkModule (handle, NSUNLINKMODULE_OPTION_NONE);
}

static HCL_INLINE void* mach_dlsym (void* handle, const char* name)
{
	mach_dlerror_str = "";
	return (void*)NSAddressOfSymbol(NSLookupSymbolInModule(handle, name));
}

static const char* mach_dlerror (void)
{
	int err_no;
	const char* err_file;
	NSLinkEditErrors err;

	if (mach_dlerror_str[0] == '\0')
		NSLinkEditError (&err, &err_no, &err_file, &mach_dlerror_str);

	return mach_dlerror_str;
}
#endif

static void dl_startup (hcl_t* hcl)
{
#if defined(USE_LTDL)
	lt_dlinit ();
#endif
}

static void dl_cleanup (hcl_t* hcl)
{
#if defined(USE_LTDL)
	lt_dlexit ();
#endif
}

static void* dl_open (hcl_t* hcl, const hcl_ooch_t* name, int flags)
{
#if defined(USE_LTDL) || defined(USE_DLFCN) || defined(USE_MACH_O_DYLD)
	hcl_bch_t stabuf[128], * bufptr;
	hcl_oow_t ucslen, bcslen, bufcapa;
	void* handle;

	#if defined(HCL_OOCH_IS_UCH)
	if (hcl_convootobcstr(hcl, name, &ucslen, HCL_NULL, &bufcapa) <= -1) return HCL_NULL;
	/* +1 for terminating null. but it's not needed because HCL_COUNTOF(HCL_DEFAULT_PFMODPREFIX)
	 * and HCL_COUNTOF(HCL_DEFAULT_PFMODPOSTIFX) include the terminating nulls. Never mind about
	 * the extra 2 characters. */
	#else
	bufcapa = hcl_count_bcstr(name);
	#endif
	bufcapa += HCL_COUNTOF(HCL_DEFAULT_PFMODDIR) + HCL_COUNTOF(HCL_DEFAULT_PFMODPREFIX) + HCL_COUNTOF(HCL_DEFAULT_PFMODPOSTFIX) + 1;

	if (bufcapa <= HCL_COUNTOF(stabuf)) bufptr = stabuf;
	else
	{
		bufptr = (hcl_bch_t*)hcl_allocmem(hcl, bufcapa * HCL_SIZEOF(*bufptr));
		if (!bufptr) return HCL_NULL;
	}

	if (flags & HCL_VMPRIM_DLOPEN_PFMOD)
	{
		hcl_oow_t len, i, xlen, dlen;

		/* opening a primitive function module - mostly libhcl-xxxx.
		 * if PFMODPREFIX is absolute, never use PFMODDIR */
		dlen = HCL_IS_PATH_ABSOLUTE(HCL_DEFAULT_PFMODPREFIX)?
			0: hcl_copy_bcstr(bufptr, bufcapa, HCL_DEFAULT_PFMODDIR);
		len = hcl_copy_bcstr(bufptr, bufcapa, HCL_DEFAULT_PFMODPREFIX);
		len += dlen;

		bcslen = bufcapa - len;
	#if defined(HCL_OOCH_IS_UCH)
		hcl_convootobcstr(hcl, name, &ucslen, &bufptr[len], &bcslen);
	#else
		bcslen = hcl_copy_bcstr(&bufptr[len], bcslen, name);
	#endif

		/* length including the directory, the prefix and the name. but excluding the postfix */
		xlen  = len + bcslen;

		for (i = len; i < xlen; i++)
		{
			/* convert a period(.) to a dash(-) */
			if (bufptr[i] == '.') bufptr[i] = '-';
		}

	retry:
		hcl_copy_bcstr (&bufptr[xlen], bufcapa - xlen, HCL_DEFAULT_PFMODPOSTFIX);

		/* both prefix and postfix attached. for instance, libhcl-xxx */
		handle = sys_dl_openext(bufptr);
		if (!handle)
		{
			HCL_DEBUG3 (hcl, "Unable to open(ext) PFMOD %hs[%js] - %hs\n", &bufptr[dlen], name, sys_dl_error());

			if (dlen > 0)
			{
				handle = sys_dl_openext(&bufptr[0]);
				if (handle) goto pfmod_open_ok;
				HCL_DEBUG3 (hcl, "Unable to open(ext) PFMOD %hs[%js] - %hs\n", &bufptr[0], name, sys_dl_error());
			}

			/* try without prefix and postfix */
			bufptr[xlen] = '\0';
			handle = sys_dl_openext(&bufptr[len]);
			if (!handle)
			{
				hcl_bch_t* dash;
				const hcl_bch_t* dl_errstr;
				dl_errstr = sys_dl_error();
				HCL_DEBUG3 (hcl, "Unable to open(ext) PFMOD %hs[%js] - %hs\n", &bufptr[len], name, dl_errstr);
				hcl_seterrbfmt (hcl, HCL_ESYSERR, "unable to open(ext) PFMOD %js - %hs", name, dl_errstr);

				dash = hcl_rfind_bchar(bufptr, hcl_count_bcstr(bufptr), '-');
				if (dash)
				{
					/* remove a segment at the back.
					 * [NOTE] a dash contained in the original name before
					 *        period-to-dash transformation may cause extraneous/wrong
					 *        loading reattempts. */
					xlen = dash - bufptr;
					goto retry;
				}
			}
			else
			{
				HCL_DEBUG3 (hcl, "Opened(ext) PFMOD %hs[%js] handle %p\n", &bufptr[len], name, handle);
			}
		}
		else
		{
		pfmod_open_ok:
			HCL_DEBUG3 (hcl, "Opened(ext) PFMOD %hs[%js] handle %p\n", &bufptr[dlen], name, handle);
		}
	}
	else
	{
		/* opening a raw shared object without a prefix and/or a postfix */
	#if defined(HCL_OOCH_IS_UCH)
		bcslen = bufcapa;
		hcl_convootobcstr(hcl, name, &ucslen, bufptr, &bcslen);
	#else
		bcslen = hcl_copy_bcstr(bufptr, bufcapa, name);
	#endif

		if (hcl_find_bchar(bufptr, bcslen, '.'))
		{
			handle = sys_dl_open(bufptr);
			if (!handle)
			{
				const hcl_bch_t* dl_errstr;
				dl_errstr = sys_dl_error();
				HCL_DEBUG2 (hcl, "Unable to open DL %hs - %hs\n", bufptr, dl_errstr);
				hcl_seterrbfmt (hcl, HCL_ESYSERR, "unable to open DL %js - %hs", name, dl_errstr);
			}
			else HCL_DEBUG2 (hcl, "Opened DL %hs handle %p\n", bufptr, handle);
		}
		else
		{
			handle = sys_dl_openext(bufptr);
			if (!handle)
			{
				const hcl_bch_t* dl_errstr;
				dl_errstr = sys_dl_error();
				HCL_DEBUG2 (hcl, "Unable to open(ext) DL %hs - %hs\n", bufptr, dl_errstr);
				hcl_seterrbfmt (hcl, HCL_ESYSERR, "unable to open(ext) DL %js - %hs", name, dl_errstr);
			}
			else HCL_DEBUG2 (hcl, "Opened(ext) DL %hs handle %p\n", bufptr, handle);
		}
	}

	if (bufptr != stabuf) hcl_freemem (hcl, bufptr);
	return handle;

#else

/* TODO: support various platforms */
	/* TODO: implemenent this */
	HCL_DEBUG1 (hcl, "Dynamic loading not implemented - cannot open %js\n", name);
	hcl_seterrbfmt (hcl, HCL_ENOIMPL, "dynamic loading not implemented - cannot open %js", name);
	return HCL_NULL;
#endif
}

static void dl_close (hcl_t* hcl, void* handle)
{
#if defined(USE_LTDL) || defined(USE_DLFCN) || defined(USE_MACH_O_DYLD)
	HCL_DEBUG1 (hcl, "Closed DL handle %p\n", handle);
	sys_dl_close (handle);

#else
	/* TODO: implemenent this */
	HCL_DEBUG1 (hcl, "Dynamic loading not implemented - cannot close handle %p\n", handle);
#endif
}

static void* dl_getsym (hcl_t* hcl, void* handle, const hcl_ooch_t* name)
{
#if defined(USE_LTDL) || defined(USE_DLFCN) || defined(USE_MACH_O_DYLD)
	hcl_bch_t stabuf[64], * bufptr;
	hcl_oow_t bufcapa, ucslen, bcslen, i;
	const hcl_bch_t* symname;
	void* sym;

	#if defined(HCL_OOCH_IS_UCH)
	if (hcl_convootobcstr(hcl, name, &ucslen, HCL_NULL, &bcslen) <= -1) return HCL_NULL;
	#else
	bcslen = hcl_count_bcstr (name);
	#endif

	if (bcslen >= HCL_COUNTOF(stabuf) - 2)
	{
		bufcapa = bcslen + 3;
		bufptr = (hcl_bch_t*)hcl_allocmem(hcl, bufcapa * HCL_SIZEOF(*bufptr));
		if (!bufptr) return HCL_NULL;
	}
	else
	{
		bufcapa = HCL_COUNTOF(stabuf);
		bufptr = stabuf;
	}

	bcslen = bufcapa - 1;
	#if defined(HCL_OOCH_IS_UCH)
	hcl_convootobcstr (hcl, name, &ucslen, &bufptr[1], &bcslen);
	#else
	bcslen = hcl_copy_bcstr(&bufptr[1], bcslen, name);
	#endif

	/* convert a period(.) to an underscore(_) */
	for (i = 1; i <= bcslen; i++) if (bufptr[i] == '.') bufptr[i] = '_';

	symname = &bufptr[1]; /* try the name as it is */

	sym = sys_dl_getsym(handle, symname);
	if (!sym)
	{
		bufptr[0] = '_';
		symname = &bufptr[0]; /* try _name */
		sym = sys_dl_getsym(handle, symname);
		if (!sym)
		{
			bufptr[bcslen + 1] = '_';
			bufptr[bcslen + 2] = '\0';

			symname = &bufptr[1]; /* try name_ */
			sym = sys_dl_getsym(handle, symname);

			if (!sym)
			{
				symname = &bufptr[0]; /* try _name_ */
				sym = sys_dl_getsym(handle, symname);
				if (!sym)
				{
					const hcl_bch_t* dl_errstr;
					dl_errstr = sys_dl_error();
					HCL_DEBUG3 (hcl, "Failed to get module symbol %js from handle %p - %hs\n", name, handle, dl_errstr);
					hcl_seterrbfmt (hcl, HCL_ENOENT, "unable to get module symbol %hs - %hs", symname, dl_errstr);

				}
			}
		}
	}

	if (sym) HCL_DEBUG3 (hcl, "Loaded module symbol %js from handle %p - %hs\n", name, handle, symname);
	if (bufptr != stabuf) hcl_freemem (hcl, bufptr);
	return sym;

#else
	/* TODO: IMPLEMENT THIS */
	HCL_DEBUG2 (hcl, "Dynamic loading not implemented - Cannot load module symbol %js from handle %p\n", name, handle);
	hcl_seterrbfmt (hcl, HCL_ENOIMPL, "dynamic loading not implemented - Cannot load module symbol %js from handle %p", name, handle);
	return HCL_NULL;
#endif
}

/* -----------------------------------------------------------------
 * EVENT CALLBACKS
 * ----------------------------------------------------------------- */

/*#define ENABLE_LOG_INITIALLY*/

static HCL_INLINE void reset_log_to_default (xtn_t* xtn)
{
#if defined(ENABLE_LOG_INITIALLY)
	xtn->log.fd = 2;
	xtn->log.fd_flag = 0;
	#if defined(HAVE_ISATTY)
	if (isatty(xtn->log.fd)) xtn->log.fd_flag |= LOGFD_TTY;
	#endif
#else
	xtn->log.fd = -1;
	xtn->log.fd_flag = 0;
#endif
}

static void cb_fini (hcl_t* hcl)
{
	xtn_t* xtn = GET_XTN(hcl);
	if ((xtn->log.fd_flag & LOGFD_OPENED_HERE) && xtn->log.fd >= 0) close (xtn->log.fd);
	reset_log_to_default (xtn);
}

static void cb_opt_set (hcl_t* hcl, hcl_option_t id, const void* value)
{
	xtn_t* xtn = GET_XTN(hcl);
	int fd;

	if (id != HCL_LOG_TARGET_BCSTR && id != HCL_LOG_TARGET_UCSTR &&
	    id != HCL_LOG_TARGET_BCS && id != HCL_LOG_TARGET_UCS) return; /* return success. not interested */

#if defined(_WIN32)
	#if defined(HCL_OOCH_IS_UCH)
	fd = _open(hcl->option.log_target_u, _O_CREAT | _O_WRONLY | _O_APPEND | _O_BINARY , 0644);
	#else
	fd = _open(hcl->option.log_target_b, _O_CREAT | _O_WRONLY | _O_APPEND | _O_BINARY , 0644);
	#endif
#else
	fd = open(hcl->option.log_target_b, O_CREAT | O_WRONLY | O_APPEND , 0644);
#endif
	if (fd == -1)
	{
		/* TODO: any warning that log file not changed? */
	}
	else
	{
		if ((xtn->log.fd_flag & LOGFD_OPENED_HERE) && xtn->log.fd >= 0) close (xtn->log.fd);

		xtn->log.fd = fd;
		xtn->log.fd_flag = LOGFD_OPENED_HERE;
	#if defined(HAVE_ISATTY)
		if (isatty(xtn->log.fd)) xtn->log.fd_flag |= LOGFD_TTY;
	#endif
	}
}

#if defined(__OS2__) && defined(TCPV40HDRS)
static int os2_socket_pair (int p[2])
{
	int x = -1, y = -1, z;
	struct sockaddr_un sa;
	PTIB tib;
	PPIB pib;
	ULONG msec, idx;

	DosGetInfoBlocks(&tib, &pib);
	DosQuerySysInfo (QSV_MS_COUNT, QSV_MS_COUNT, &msec, HCL_SIZEOF(msec));

	x = socket(PF_OS2, SOCK_STREAM, 0);
	if (x <= -1) goto oops;

	idx = msec;

attempt_to_bind:
	HCL_MEMSET (&sa, 0, HCL_SIZEOF(sa));
	sa.sun_family = AF_OS2;

	/* OS/2 mandates the socket name should begin with \socket\ */
	sprintf (sa.sun_path, "\\socket\\hcl-%08lx-%08lx-%08lx", (unsigned long int)pib->pib_ulpid, (unsigned long int)tib->tib_ptib2->tib2_ultid, (unsigned long int)idx);

	if (bind(x, (struct sockaddr*)&sa, HCL_SIZEOF(sa)) <= -1) 
	{
		if (sock_errno() != SOCEADDRINUSE) goto oops;
		if (idx - msec > 9999) goto oops; /* failure after many attempts */

		idx++;
		goto attempt_to_bind;
	}
	if (listen(x, 1) <= -1) goto oops;

	y = socket(PF_OS2, SOCK_STREAM, 0);
	if (y <= -1) goto oops;
	if (connect(y, (struct sockaddr*)&sa, HCL_SIZEOF(sa)) <= -1) goto oops;
	z = accept(x, HCL_NULL, HCL_NULL);
	if (z <= -1) goto oops;

	soclose (x);
	p[0] = z;
	p[1] = y;
	return 0;
	
oops:
	if (y >= 0) soclose (y);
	if (x >= 0) soclose (x);
	return -1;
}
#endif

static int open_pipes (hcl_t* hcl, int p[2])
{
#if defined(_WIN32)
	u_long flags;
#else
	int flags;
#endif

#if defined(_WIN32)
	if (_pipe(p, 256, _O_BINARY | _O_NOINHERIT) == -1)
	{
		hcl_seterrbfmtwithsyserr (hcl, 0, errno, "unable to create pipes");
		return -1;
	}

#elif defined(__OS2__)
	#if defined(TCPV40HDRS)
	/* neither pipe nor socketpair available */
	if (os2_socket_pair(p) == -1)
	#else
	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, p) == -1)
	#endif
	{
		hcl_seterrbfmtwithsyserr (hcl, 2, sock_errno(), "unable to create pipes");
		return -1;
	}
#elif defined(HAVE_PIPE2) && defined(O_CLOEXEC) && defined(O_NONBLOCK)
	if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == -1)
	{
		hcl_seterrbfmtwithsyserr (hcl, 0, errno, "unable to create pipes");
		return -1;
	}

#else
	if (pipe(p) == -1)
	{
		hcl_seterrbfmtwithsyserr (hcl, 0, errno, "unable to create pipes");
		return -1;
	}
#endif

#if defined(_WIN32)
	flags = 1;
	ioctl (p[0], FIONBIO, &flags);
	ioctl (p[1], FIONBIO, &flags);
#elif defined(__OS2__)
	flags = 1; /* don't block */
	ioctl (p[0], FIONBIO, (char*)&flags, HCL_SIZEOF(flags));
	ioctl (p[1], FIONBIO, (char*)&flags, HCL_SIZEOF(flags));
#elif defined(HAVE_PIPE2) && defined(O_CLOEXEC) && defined(O_NONBLOCK)
		/* do nothing */
#else
	#if defined(FD_CLOEXEC)
	flags = fcntl(p[0], F_GETFD);
	if (flags >= 0) fcntl (p[0], F_SETFD, flags | FD_CLOEXEC);
	flags = fcntl(p[1], F_GETFD);
	if (flags >= 0) fcntl (p[1], F_SETFD, flags | FD_CLOEXEC);
	#endif
	#if defined(O_NONBLOCK)
	flags = fcntl(p[0], F_GETFL);
	if (flags >= 0) fcntl (p[0], F_SETFL, flags | O_NONBLOCK);
	flags = fcntl(p[1], F_GETFL);
	if (flags >= 0) fcntl (p[1], F_SETFL, flags | O_NONBLOCK);
	#endif
#endif

	return 0;
}

static void close_pipes (hcl_t* hcl, int p[2])
{
#if defined(_WIN32)
	_close (p[0]);
	_close (p[1]);
#elif defined(__OS2__)
	soclose (p[0]);
	soclose (p[1]);
#else
	close (p[0]);
	close (p[1]);
#endif
	p[0] = -1;
	p[1] = -1;
}

static int cb_vm_startup (hcl_t* hcl)
{
	xtn_t* xtn = GET_XTN(hcl);
	int sigfd_pcount = 0;
	int iothr_pcount = 0, flags;

#if defined(_WIN32)
	xtn->waitable_timer = CreateWaitableTimer(HCL_NULL, TRUE, HCL_NULL);
#endif

#if defined(USE_DEVPOLL)
	xtn->ep = open("/dev/poll", O_RDWR);
	if (xtn->ep == -1)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG1 (hcl, "Cannot create devpoll - %hs\n", strerror(errno));
		goto oops;
	}

	#if defined(FD_CLOEXEC)
	flags = fcntl(xtn->ep, F_GETFD);
	if (flags >= 0) fcntl (xtn->ep, F_SETFD, flags | FD_CLOEXEC);
	#endif

#elif defined(USE_KQUEUE)
	#if defined(HAVE_KQUEUE1) && defined(O_CLOEXEC)
	xtn->ep = kqueue1(O_CLOEXEC);
	if (xtn->ep == -1) xtn->ep = kqueue();
	#else
	xtn->ep = kqueue();
	#endif
	if (xtn->ep == -1)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG1 (hcl, "Cannot create kqueue - %hs\n", strerror(errno));
		goto oops;
	}

	#if defined(FD_CLOEXEC)
	flags = fcntl(xtn->ep, F_GETFD);
	if (flags >= 0 && !(flags & FD_CLOEXEC)) fcntl (xtn->ep, F_SETFD, flags | FD_CLOEXEC);
	#endif

#elif defined(USE_EPOLL)
	#if defined(HAVE_EPOLL_CREATE1) && defined(EPOLL_CLOEXEC)
	xtn->ep = epoll_create1(EPOLL_CLOEXEC);
	if (xtn->ep == -1) xtn->ep = epoll_create(1024);
	#else
	xtn->ep = epoll_create(1024);
	#endif
	if (xtn->ep == -1)
	{
		hcl_seterrwithsyserr (hcl, 0, errno);
		HCL_DEBUG1 (hcl, "Cannot create epoll - %hs\n", strerror(errno));
		goto oops;
	}

	#if defined(FD_CLOEXEC)
	flags = fcntl(xtn->ep, F_GETFD);
	if (flags >= 0 && !(flags & FD_CLOEXEC)) fcntl (xtn->ep, F_SETFD, flags | FD_CLOEXEC);
	#endif

#elif defined(USE_POLL)

	MUTEX_INIT (&xtn->ev.reg.pmtx);

#elif defined(USE_SELECT)
	FD_ZERO (&xtn->ev.reg.rfds);
	FD_ZERO (&xtn->ev.reg.wfds);
	xtn->ev.reg.maxfd = -1;
	MUTEX_INIT (&xtn->ev.reg.smtx);
#endif /* USE_DEVPOLL */

	if (open_pipes(hcl, xtn->sigfd.p) <= -1) goto oops;
	sigfd_pcount = 2;

#if defined(USE_THREAD)
	if (open_pipes(hcl, xtn->iothr.p) <= -1) goto oops;
	iothr_pcount = 2;

	if (_add_poll_fd(hcl, xtn->iothr.p[0], XPOLLIN) <= -1) goto oops;

	pthread_mutex_init (&xtn->ev.mtx, HCL_NULL);
	pthread_cond_init (&xtn->ev.cnd, HCL_NULL);
	pthread_cond_init (&xtn->ev.cnd2, HCL_NULL);
	xtn->ev.halting = 0;

	xtn->iothr.abort = 0;
	xtn->iothr.up = 0;
	/*pthread_create (&xtn->iothr, HCL_NULL, iothr_main, hcl);*/

#endif /* USE_THREAD */

	xtn->vm_running = 1;
	return 0;

oops:
#if defined(USE_THREAD)
	if (iothr_pcount > 0)
	{
		close (xtn->iothr.p[0]);
		close (xtn->iothr.p[1]);
	}
#endif

	if (sigfd_pcount > 0)
	{
		close (xtn->sigfd.p[0]);
		close (xtn->sigfd.p[1]);
	}

#if defined(USE_DEVPOLL) || defined(USE_EPOLL)
	if (xtn->ep >= 0)
	{
		close (xtn->ep);
		xtn->ep = -1;
	}
#endif

	return -1;
}

static void cb_vm_cleanup (hcl_t* hcl)
{
	xtn_t* xtn = GET_XTN(hcl);

	xtn->vm_running = 0;

#if defined(_WIN32)
	if (xtn->waitable_timer)
	{
		CloseHandle (xtn->waitable_timer);
		xtn->waitable_timer = HCL_NULL;
	}
#endif

#if defined(USE_THREAD)
	if (xtn->iothr.up)
	{
		xtn->iothr.abort = 1;
		write (xtn->iothr.p[1], "Q", 1);
		pthread_cond_signal (&xtn->ev.cnd);
		pthread_join (xtn->iothr.thr, HCL_NULL);
		xtn->iothr.up = 0;
	}
	pthread_cond_destroy (&xtn->ev.cnd);
	pthread_cond_destroy (&xtn->ev.cnd2);
	pthread_mutex_destroy (&xtn->ev.mtx);

	_del_poll_fd (hcl, xtn->iothr.p[0]);
	close_pipes (hcl, xtn->iothr.p);
#endif /* USE_THREAD */

	close_pipes (hcl, xtn->sigfd.p);

#if defined(USE_DEVPOLL)
	if (xtn->ep >= 0)
	{
		close (xtn->ep);
		xtn->ep = -1;
	}
	/*destroy_poll_data_space (hcl);*/
#elif defined(USE_KQUEUE)
	if (xtn->ep >= 0)
	{
		close (xtn->ep);
		xtn->ep = -1;
	}
#elif defined(USE_EPOLL)
	if (xtn->ep >= 0)
	{
		close (xtn->ep);
		xtn->ep = -1;
	}
#elif defined(USE_POLL)
	if (xtn->ev.reg.ptr)
	{
		hcl_freemem (hcl, xtn->ev.reg.ptr);
		xtn->ev.reg.ptr = HCL_NULL;
		xtn->ev.reg.len = 0;
		xtn->ev.reg.capa = 0;
	}
	if (xtn->ev.buf)
	{
		hcl_freemem (hcl, xtn->ev.buf);
		xtn->ev.buf = HCL_NULL;
	}
	/*destroy_poll_data_space (hcl);*/
	MUTEX_DESTROY (&xtn->ev.reg.pmtx);
#elif defined(USE_SELECT)
	FD_ZERO (&xtn->ev.reg.rfds);
	FD_ZERO (&xtn->ev.reg.wfds);
	xtn->ev.reg.maxfd = -1;
	MUTEX_DESTROY (&xtn->ev.reg.smtx);
#endif
}

/* -----------------------------------------------------------------
 * STANDARD HCL
 * ----------------------------------------------------------------- */

hcl_t* hcl_openstdwithmmgr (hcl_mmgr_t* mmgr, hcl_oow_t xtnsize, hcl_errnum_t* errnum)
{
	hcl_t* hcl;
	hcl_vmprim_t vmprim;
	hcl_cb_t cb;

	HCL_MEMSET (&vmprim, 0, HCL_SIZEOF(vmprim));
	vmprim.alloc_heap = alloc_heap;
	vmprim.free_heap = free_heap;
	vmprim.log_write = log_write;
	vmprim.syserrstrb = _syserrstrb;
	vmprim.assertfail = _assertfail;
	vmprim.dl_startup = dl_startup;
	vmprim.dl_cleanup = dl_cleanup;
	vmprim.dl_open = dl_open;
	vmprim.dl_close = dl_close;
	vmprim.dl_getsym = dl_getsym;
	vmprim.vm_gettime = vm_gettime;
	vmprim.vm_muxadd = vm_muxadd;
	vmprim.vm_muxdel = vm_muxdel;
	vmprim.vm_muxmod = vm_muxmod;
	vmprim.vm_muxwait = vm_muxwait;
	vmprim.vm_sleep = vm_sleep;

	hcl = hcl_open(mmgr, HCL_SIZEOF(xtn_t) + xtnsize, &vmprim, errnum);
	if (HCL_UNLIKELY(!hcl)) return HCL_NULL;

	/* adjust the object size by the sizeof xtn_t so that moo_getxtn() returns the right pointer. */
	hcl->_instsize += HCL_SIZEOF(xtn_t);

	reset_log_to_default (GET_XTN(hcl));

	HCL_MEMSET (&cb, 0, HCL_SIZEOF(cb));
	cb.fini = cb_fini;
	cb.opt_set = cb_opt_set;
	cb.vm_startup = cb_vm_startup;
	cb.vm_cleanup = cb_vm_cleanup;
	if (hcl_regcb(hcl, &cb) == HCL_NULL)
	{
		if (errnum) *errnum = hcl_geterrnum(hcl);
		hcl_close (hcl);
		return HCL_NULL;
	}

	return hcl;
}

hcl_t* hcl_openstd (hcl_oow_t xtnsize, hcl_errnum_t* errnum)
{
	return hcl_openstdwithmmgr(&sys_mmgr, xtnsize, errnum);
}
