/*
    Copyright (c) 2006-2020 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 "mod-sys.h"
#include "hawk-prv.h"
#include <hawk-dir.h>

#if defined(_WIN32)
#	include <windows.h>
#	include <process.h>
#	include <tchar.h>
#elif defined(__OS2__)
#	define INCL_DOSPROCESS
#	define INCL_DOSEXCEPTIONS
#	define INCL_ERRORS
#	include <os2.h>
#elif defined(__DOS__)
#	include <dos.h>
#else
#	if !defined(_GNU_SOURCE)
#		define _GNU_SOURCE
#	endif

#	include "syscall.h"
#	if defined(HAVE_SYS_EPOLL_H)
#		include <sys/epoll.h>
#		if defined(HAVE_EPOLL_CREATE)
#			define USE_EPOLL
#		endif
#	endif

#	include <termios.h>
#	include <sys/socket.h>

#	define ENABLE_SYSLOG
#	include <syslog.h>

#endif

#include <stdlib.h> /* getenv, system */
#include <time.h>
#include <errno.h>
#include <string.h>

#define DEFAULT_MODE (0777)

#define CLOSE_KEEPFD (1 << 0)

/* these must not conflict with F_RDLCK, F_WRLCK, F_UNLCK */
#define FLOCK_GET (1 << 30)
#define FLOCK_WAIT (1 << 31)

#if defined(SOCK_CLOEXEC)
#	define X_SOCK_CLOEXEC SOCK_CLOEXEC
#else
#	define X_SOCK_CLOEXEC (0) /* 0 is effectless for a bit flag */
#endif

#if defined(SOCK_NONBLOCK)
#	define X_SOCK_NONBLOCK SOCK_NONBLOCK
#else
#	define X_SOCK_NONBLOCK (0) /* 0 is effectless for a bit flag */
#endif

#if defined(SO_REUSEPORT)
#	define X_SO_REUSEPORT SO_REUSEPORT
#else
#	define X_SO_REUSEPORT (9999999) /* this must be a non-existent code */
#endif

#if defined(IUCLC)
#	define X_IUCLC IUCLC
#else
#	define X_IUCLC (0)
#endif

#if defined(IUTF8)
#	define X_IUTF8 IUTF8
#else
#	define X_IUTF8 (0)
#endif

#if defined(OXTABS)
#	define X_OXTABS OXTABS
#else
#	define X_OXTABS (0)
#endif

#if defined(ONOEOT)
#	define X_ONOEOT ONOEOT
#else
#	define X_ONOEOT (0)
#endif

/*
 * IMPLEMENTATION NOTE:
 *   - hard failure only if it cannot make a final return value. (e.g. fnc_errmsg, fnc_fork, fnc_getpid)
 *   - soft failure if it cannot make a value for pass-by-referece argument (e.g. fnc_pipe)
 *   - soft failure for all other types of errors
 */

/* ------------------------------------------------------------------------ */

typedef enum syslog_type_t syslog_type_t;
enum syslog_type_t
{
	SYSLOG_LOCAL,
	SYSLOG_REMOTE,
};

struct mod_ctx_t
{
	hawk_rbt_t* rtxtab;
};
typedef struct mod_ctx_t mod_ctx_t;

/* ------------------------------------------------------------------------ */

enum sys_node_data_type_t
{
	SYS_NODE_DATA_TYPE_FILE = (1 << 0),
	SYS_NODE_DATA_TYPE_SCK  = (1 << 1),
	SYS_NODE_DATA_TYPE_DIR  = (1 << 2),
	SYS_NODE_DATA_TYPE_MUX  = (1 << 3)
};
typedef enum sys_node_data_type_t sys_node_data_type_t;

enum sys_node_data_flag_t
{
	SYS_NODE_DATA_FLAG_IN_MUX = (1 << 0)
};
typedef enum sys_node_data_flag_t sys_node_data_flag_t;


struct sys_node_data_file_t
{
	int fd;
	void* mux; /* if SYS_NODE_DATA_FLAG_IN_MUX is set, this is set to a valid pointer. it is of the void* type since sys_node_t is not available yet. */
	void* x_prev;
	void* x_next;
};
typedef struct sys_node_data_file_t sys_node_data_file_t;

struct sys_node_data_mux_t
{
#if defined(USE_EPOLL)
	int fd;
	struct epoll_event* x_evt;
#endif
	void* x_first;
	void* x_last;
	hawk_oow_t x_count;
	hawk_oow_t x_evt_max;
	hawk_oow_t x_evt_count;
};
typedef struct sys_node_data_mux_t sys_node_data_mux_t;

struct sys_node_data_t
{
	sys_node_data_type_t type;
	int flags;
	union
	{
		sys_node_data_file_t file;
		hawk_dir_t* dir;
		sys_node_data_mux_t mux;
	} u;
};
typedef struct sys_node_data_t sys_node_data_t;

struct sys_list_data_t
{
	hawk_ooch_t errmsg[256];
	hawk_bch_t* readbuf;
	hawk_oow_t readbuf_capa;
	hawk_oow_t readbuf_len;
	hawk_ooch_t skadbuf[2][256];
};
typedef struct sys_list_data_t sys_list_data_t;

#define __IDMAP_NODE_T_DATA sys_node_data_t ctx;
#define __IDMAP_LIST_T_DATA sys_list_data_t ctx;
#define __IDMAP_LIST_T sys_list_t
#define __IDMAP_NODE_T sys_node_t
#define __INIT_IDMAP_LIST __init_sys_list
#define __FINI_IDMAP_LIST __fini_sys_list
#define __MAKE_IDMAP_NODE __new_sys_node
#define __FREE_IDMAP_NODE __free_sys_node
#include "idmap-imp.h"

struct rtx_data_t
{
	sys_list_t sys_list;

	struct
	{
		hawk_uint8_t __static_buf[256];
		hawk_uint8_t* ptr;
		hawk_oow_t capa;
		hawk_oow_t len;
	} pack;


	/* syslog data */
	struct
	{
		syslog_type_t type;
		char* ident;
		hawk_skad_t skad;
		int syslog_opened; // has openlog() been called?
		int opt;
		int fac;
		int sck;
		hawk_becs_t* dmsgbuf;
	} log;
};
typedef struct rtx_data_t rtx_data_t;

enum mux_ctl_cmd_t
{
#if defined(USE_EPOLL)
	MUX_CTL_ADD = EPOLL_CTL_ADD,
	MUX_CTL_DEL = EPOLL_CTL_DEL,
	MUX_CTL_MOD = EPOLL_CTL_MOD
#else
	MUX_CTL_ADD,
	MUX_CTL_DEL,
	MUX_CTL_MOD
#endif
};
typedef enum mux_ctl_cmd_t mux_ctl_cmd_t;

enum mux_evt_code_t
{
#if defined(USE_EPOLL)
	MUX_EVT_IN  = EPOLLIN,
	MUX_EVT_OUT = EPOLLOUT,
	MUX_EVT_ERR = EPOLLERR,
	MUX_EVT_HUP = EPOLLHUP
#else
	MUX_EVT_IN  = (1 << 0),
	MUX_EVT_OUT = (1 << 1),
	MUX_EVT_ERR = (1 << 2),
	MUX_EVT_HUP = (1 << 3)
#endif
};

/* ------------------------------------------------------------------------ */

#define ERRNUM_TO_RC(errnum) (-((hawk_int_t)errnum))

static hawk_int_t copy_error_to_sys_list (hawk_rtx_t* rtx, sys_list_t* sys_list)
{
	hawk_errnum_t errnum = hawk_rtx_geterrnum(rtx);
	hawk_copy_oocstr(sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), hawk_rtx_geterrmsg(rtx));
	return ERRNUM_TO_RC(errnum);
}

static hawk_int_t set_error_on_sys_list (hawk_rtx_t* rtx, sys_list_t* sys_list, hawk_errnum_t errnum, const hawk_ooch_t* errfmt, ...)
{
	va_list ap;
	if (errfmt)
	{
		va_start (ap, errfmt);
		hawk_rtx_vfmttooocstr(rtx, sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), errfmt, ap);
		va_end (ap);
	}
	else
	{
		hawk_rtx_fmttooocstr(rtx, sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), HAWK_T("%js"), hawk_geterrstr(hawk_rtx_gethawk(rtx))(errnum));
	}
	return ERRNUM_TO_RC(errnum);
}

static hawk_int_t set_error_on_sys_list_with_errno (hawk_rtx_t* rtx, sys_list_t* sys_list, const hawk_ooch_t* title)
{
	int err = errno;

	if (title)
		hawk_rtx_fmttooocstr(rtx, sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), HAWK_T("%js - %hs"), title, strerror(err));
	else
		hawk_rtx_fmttooocstr(rtx, sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), HAWK_T("%hs"), strerror(err));
	return ERRNUM_TO_RC(hawk_syserr_to_errnum(err));
}

static void set_errmsg_on_sys_list (hawk_rtx_t* rtx, sys_list_t* sys_list, const hawk_ooch_t* errfmt, ...)
{
	if (errfmt)
	{
		va_list ap;
		va_start (ap, errfmt);
		hawk_rtx_vfmttooocstr(rtx, sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), errfmt, ap);
		va_end (ap);
	}
	else
	{
		hawk_copy_oocstr (sys_list->ctx.errmsg, HAWK_COUNTOF(sys_list->ctx.errmsg), hawk_rtx_geterrmsg(rtx));
	}
}

/* ------------------------------------------------------------------------ */

static sys_node_t* new_sys_node_fd (hawk_rtx_t* rtx, sys_list_t* list, int fd)
{
	sys_node_t* node;

	node = __new_sys_node(rtx, list);
	if (!node) return HAWK_NULL;

	node->ctx.type = SYS_NODE_DATA_TYPE_FILE;
	node->ctx.flags = 0;
	node->ctx.u.file.fd = fd;
	return node;
}

static sys_node_t* new_sys_node_dir (hawk_rtx_t* rtx, sys_list_t* list, hawk_dir_t* dir)
{
	sys_node_t* node;

	node = __new_sys_node(rtx, list);
	if (!node) return HAWK_NULL;

	node->ctx.type = SYS_NODE_DATA_TYPE_DIR;
	node->ctx.flags = 0;
	node->ctx.u.dir = dir;
	return node;
}

static sys_node_t* new_sys_node_mux (hawk_rtx_t* rtx, sys_list_t* list, int fd)
{
	sys_node_t* node;

	node = __new_sys_node(rtx, list);
	if (!node) return HAWK_NULL;

	node->ctx.type = SYS_NODE_DATA_TYPE_MUX;
	node->ctx.flags = 0;
#if defined(USE_EPOLL)
	node->ctx.u.mux.fd = fd;
#endif
	return node;
}

static void chain_sys_node_to_mux_node (sys_node_t* mux_node, sys_node_t* node)
{
	sys_node_data_mux_t* mux_data = &mux_node->ctx.u.mux;
	sys_node_data_file_t* file_data = &node->ctx.u.file;

	HAWK_ASSERT(!(node->ctx.flags & SYS_NODE_DATA_FLAG_IN_MUX));
	node->ctx.flags |= SYS_NODE_DATA_FLAG_IN_MUX;

	file_data->mux = mux_node;
	file_data->x_prev = HAWK_NULL;
	file_data->x_next = mux_data->x_first;
	if (mux_data->x_first) ((sys_node_t*)mux_data->x_first)->ctx.u.file.x_prev = node;
	else mux_data->x_last = node;
	mux_data->x_first = node;
	mux_data->x_count++;
}

static void unchain_sys_node_from_mux_node (sys_node_t* mux_node, sys_node_t* node)
{
	sys_node_data_mux_t* mux_data = &mux_node->ctx.u.mux;
	sys_node_data_file_t* file_data = &node->ctx.u.file;

	HAWK_ASSERT(node->ctx.flags & SYS_NODE_DATA_FLAG_IN_MUX);
	node->ctx.flags &= ~SYS_NODE_DATA_FLAG_IN_MUX;

	if (file_data->x_prev) ((sys_node_t*)file_data->x_prev)->ctx.u.file.x_next = file_data->x_next;
	else mux_data->x_first = file_data->x_next;
	if (file_data->x_next) ((sys_node_t*)file_data->x_next)->ctx.u.file.x_prev = file_data->x_prev;
	else mux_data->x_last = file_data->x_prev;
	mux_data->x_count--;

	file_data->mux = HAWK_NULL;
}

static void nullify_mux_data (sys_node_data_mux_t* mux_data, hawk_int_t node_id)
{
	hawk_oow_t i;

	for (i = 0; i < mux_data->x_evt_count; i++)
	{
	#if defined(USE_EPOLL)
		if (mux_data->x_evt[i].data.ptr && node_id == ((sys_node_t*)mux_data->x_evt[i].data.ptr)->id)
		{
			/* nullify the event in the event array to prevent normal access by sys::getmuxevt() */
			mux_data->x_evt[i].data.ptr = HAWK_NULL;
		}
	#endif
	}
}

static void del_from_mux (hawk_rtx_t* rtx, sys_node_t* fd_node)
{
	if (fd_node->ctx.flags & SYS_NODE_DATA_FLAG_IN_MUX)
	{
		sys_node_t* mux_node;
	#if defined(USE_EPOLL)
		struct epoll_event ev;
	#endif

		switch (fd_node->ctx.type)
		{
			case SYS_NODE_DATA_TYPE_FILE:
			case SYS_NODE_DATA_TYPE_SCK:
				mux_node = (sys_node_t*)fd_node->ctx.u.file.mux;
			#if defined(USE_EPOLL)
				epoll_ctl (mux_node->ctx.u.mux.fd, MUX_CTL_DEL, fd_node->ctx.u.file.fd, &ev);
			#endif
				nullify_mux_data (&mux_node->ctx.u.mux, fd_node->id);
				unchain_sys_node_from_mux_node (mux_node, fd_node);
				break;

			default:
				/* do nothing */
				fd_node->ctx.flags &= ~SYS_NODE_DATA_FLAG_IN_MUX;
				break;
		}
	}
}

static void purge_mux_members (hawk_rtx_t* rtx, sys_node_t* mux_node)
{
	while (mux_node->ctx.u.mux.x_first)
	{
		del_from_mux(rtx, mux_node->ctx.u.mux.x_first);
	}
}

static void free_sys_node (hawk_rtx_t* rtx, sys_list_t* list, sys_node_t* node)
{
	switch (node->ctx.type)
	{
		case SYS_NODE_DATA_TYPE_FILE:
		case SYS_NODE_DATA_TYPE_SCK:
			if (node->ctx.u.file.fd >= 0)
			{
				del_from_mux(rtx, node);
				close(node->ctx.u.file.fd);
				node->ctx.u.file.fd = -1;
			}
			break;

		case SYS_NODE_DATA_TYPE_DIR:
			if (node->ctx.u.dir)
			{
				hawk_dir_close(node->ctx.u.dir);
				node->ctx.u.dir = HAWK_NULL;
			}
			break;

		case SYS_NODE_DATA_TYPE_MUX:
		#if defined(USE_EPOLL)
			if (node->ctx.u.mux.fd >= 0)
			{
				/* TODO: delete all member FILE and SCK from mux */
				purge_mux_members(rtx, node);
				close(node->ctx.u.mux.fd);
				node->ctx.u.mux.fd = -1;

				if (node->ctx.u.mux.x_evt)
				{
					hawk_rtx_freemem(rtx, node->ctx.u.mux.x_evt);
					node->ctx.u.mux.x_evt = HAWK_NULL;
				}
				node->ctx.u.mux.x_evt_max = 0;
				node->ctx.u.mux.x_evt_count = 0;
			}
		#endif
			break;
	}
	__free_sys_node(rtx, list, node);
}

/* ------------------------------------------------------------------------ */

static HAWK_INLINE rtx_data_t* rtx_to_data (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
        mod_ctx_t* mctx = (mod_ctx_t*)fi->mod->ctx;
        hawk_rbt_pair_t* pair;
        pair = hawk_rbt_search(mctx->rtxtab, &rtx, HAWK_SIZEOF(rtx));
        HAWK_ASSERT(pair != HAWK_NULL);
        return (rtx_data_t*)HAWK_RBT_VPTR(pair);
}

static HAWK_INLINE sys_list_t* rtx_to_sys_list (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return &(rtx_to_data(rtx, fi)->sys_list);
}

static HAWK_INLINE sys_node_t* get_sys_list_node (sys_list_t* sys_list, hawk_int_t id)
{
	if (id < 0 || id >= sys_list->map.high || !sys_list->map.tab[id]) return HAWK_NULL;
	return sys_list->map.tab[id];
}

/* ------------------------------------------------------------------------ */

static sys_node_t* get_sys_list_node_with_arg (hawk_rtx_t* rtx, sys_list_t* sys_list, hawk_val_t* arg, int node_type, hawk_int_t* rx)
{
	hawk_int_t id;
	sys_node_t* sys_node;

	if (hawk_rtx_valtoint(rtx, arg, &id) <= -1)
	{
		*rx = ERRNUM_TO_RC(hawk_rtx_geterrnum(rtx));
		set_errmsg_on_sys_list(rtx, sys_list, HAWK_T("illegal handle value"));
		return HAWK_NULL;
	}
	else if (!(sys_node = get_sys_list_node(sys_list, id)))
	{
		*rx = ERRNUM_TO_RC(HAWK_EINVAL);
		set_errmsg_on_sys_list(rtx, sys_list, HAWK_T("invalid handle - %zd"), (hawk_oow_t)id);
		return HAWK_NULL;
	}

	if (!(sys_node->ctx.type & node_type))
	{
		/* the handle is found but is not of the desired type */
		*rx = ERRNUM_TO_RC(HAWK_EINVAL);
		set_errmsg_on_sys_list(rtx, sys_list, HAWK_T("wrong handle type"));
		return HAWK_NULL;
	}

	return sys_node;
}

/* ------------------------------------------------------------------------ */

static int fnc_errmsg (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	retv = hawk_rtx_makestrvalwithoocstr(rtx, sys_list->ctx.errmsg);
	if (!retv) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

/* ------------------------------------------------------------------------ */

static int fnc_close (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);
	hawk_int_t cflags = 0;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);

	if (sys_node)
	{
		/* although free_sys_node can handle other types, sys::close() is allowed to
		 * close nodes of the SYS_NODE_DATA_TYPE_FILE type only */
		if (hawk_rtx_getnargs(rtx) >= 2 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &cflags) <= -1 || cflags < 0)) cflags = 0;

		if (cflags & CLOSE_KEEPFD)  /* this flag applies to file descriptors only */
		{
			sys_node->ctx.u.file.fd = -1; /* you may leak the original file descriptor. */
		}

		free_sys_node(rtx, sys_list, sys_node);
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/*
  BEGIN {
     f = sys::open("/tmp/test.txt", sys::O_RDONLY);
     while (sys::read(f, x, 10) > 0) printf (@b"%s", x);
     sys::close(f);
  }
*/

static int fnc_open (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;

	hawk_int_t rx, oflags = 0, mode = DEFAULT_MODE;
	int fd;
	hawk_bch_t* pstr;
	hawk_oow_t plen;
	hawk_val_t* a0;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &oflags) <= -1 || oflags < 0) oflags = O_RDONLY;
	if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &mode) <= -1 || mode < 0)) mode = DEFAULT_MODE;

#if defined(O_LARGEFILE)
	oflags |= O_LARGEFILE;
#endif

	a0 = hawk_rtx_getarg(rtx, 0);
	pstr = hawk_rtx_getvalbcstr(rtx, a0, &plen);
	if (pstr)
	{
		fd = open(pstr, oflags, mode);
		hawk_rtx_freevalbcstr(rtx, a0, pstr);

		if (fd >= 0)
		{
			sys_node_t* new_node;

			new_node = new_sys_node_fd(rtx, sys_list, fd);
			if (!new_node)
			{
				close(fd);
				goto fail;
			}
			rx = new_node->id;
			HAWK_ASSERT(rx >= 0);
		}
		else
		{
			rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_T("unable to open"));
		}
	}
	else
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
	}

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/*
	a = sys::openfd(1);
	sys::write(a, @b"let me write something here\n");
	sys::close(a, sys::C_KEEPFD); ## set C_KEEPFD to release 1 without closing it.
	##sys::close(a);
	print "done\n";
*/

static int fnc_openfd (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	/* wrap a raw system file descriptor into the internal management node */

	sys_list_t* sys_list;
	hawk_int_t rx;
	hawk_int_t fd;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &fd) <= -1)
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
	}
	else if (fd >= 0 && fd <= HAWK_TYPE_MAX(int))
	{
		sys_node_t* sys_node;

		sys_node = new_sys_node_fd(rtx, sys_list, fd);
		if (!sys_node) goto fail;

		rx = sys_node->id;
		HAWK_ASSERT(rx >= 0);
	}
	else
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_T("invalid file descriptor %jd"), (hawk_intmax_t)fd);
	}

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}


/* sys::read(sck, buf[, limit, [, delim]]);
 *
 * [NOTE]
 * If delim is specified, sys::read() may keep some residue data for subsequent calls.
 * sys::recvfrom() discards the residue data if it is called  after sys::read().
 */
static int fnc_read (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_int_t reqsize = 8192;
	hawk_bci_t delim = HAWK_BCI_EOF;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &reqsize) <= -1 || reqsize <= 0)) reqsize = 8192;
		if (reqsize > HAWK_INT_MAX) reqsize = HAWK_INT_MAX;

		if (reqsize > sys_list->ctx.readbuf_capa)
		{
			hawk_bch_t* tmp = hawk_rtx_reallocmem(rtx, sys_list->ctx.readbuf, reqsize);
			if (!tmp)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}
			sys_list->ctx.readbuf = tmp;
			sys_list->ctx.readbuf_capa = reqsize;
		}

		if (hawk_rtx_getnargs(rtx) >= 4)
		{
			hawk_bch_t* str;
			hawk_oow_t len;
			hawk_val_t* a3 = hawk_rtx_getarg(rtx, 3);

			str = hawk_rtx_getvalbcstr(rtx, a3, &len);
			if (!str)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}


			if (len >= 1) delim = str[0];
			hawk_rtx_freevalbcstr(rtx, a3, str);
		}

		if (sys_list->ctx.readbuf_len > 0 && delim != HAWK_BCI_EOF)
		{
			/* the read buffer has some residue data and the delimiter has been specified */
			hawk_int_t i;
			for (i = 0; i < sys_list->ctx.readbuf_len; i++)
			{
				if (sys_list->ctx.readbuf[i] == delim)
				{
					/* the residue data contains the delimiter */
					rx = i + 1;
					goto make_val_1;
				}
			}
		}

		/* check the residue data is bigger than the maximum data size requested */
		if (sys_list->ctx.readbuf_len >= reqsize) goto make_val_0;

		/* invoke the read system call */
		rx = read(sys_node->ctx.u.file.fd, &sys_list->ctx.readbuf[sys_list->ctx.readbuf_len], reqsize - sys_list->ctx.readbuf_len);
		if (rx <= 0)
		{
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_T("unable to read"));
			goto done;
		}
		else
		{
			hawk_val_t* sv;
			int x;

			sys_list->ctx.readbuf_len += rx;

		make_val_0:
			/* determine the data size to return */
			rx = reqsize <= sys_list->ctx.readbuf_len? reqsize: sys_list->ctx.readbuf_len;
			if (delim != HAWK_BCI_EOF)
			{
				/* if the delimiter is specified, check if the data size can be shortened
				 * by finding the delimiter */
				hawk_int_t i;
				for (i = 0; i < rx; i++)
				{
					if (sys_list->ctx.readbuf[i] == delim)
					{
						rx = i + 1;
						break;
					}
				}
			}

		make_val_1:
			sv = hawk_rtx_makembsvalwithbchars(rtx, sys_list->ctx.readbuf, rx);
			if (!sv)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}

			if (rx < sys_list->ctx.readbuf_len)
			{
				HAWK_MEMMOVE (&sys_list->ctx.readbuf[0], &sys_list->ctx.readbuf[rx],
				              (sys_list->ctx.readbuf_len - rx) * HAWK_SIZEOF(hawk_bch_t));
				sys_list->ctx.readbuf_len -= rx;
			}
			else sys_list->ctx.readbuf_len =  0;

			hawk_rtx_refupval(rtx, sv);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), sv);
			hawk_rtx_refdownval(rtx, sv);
			if (x <= -1)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}
		}
	}

done:
	/* the value in 'rx' never exceeds HAWK_INT_MAX as 'reqsize' has been limited to
	 * it before the call to 'read'. so it's safe not to check the result of hawk_rtx_makeintval() */
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_write (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_bch_t* dptr;
		hawk_oow_t dlen;
		hawk_int_t startpos = 0, maxlen = HAWK_TYPE_MAX(hawk_int_t);
		hawk_val_t* a1;

		if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &startpos) <= -1 || startpos < 0)) startpos = 0;
		else if (startpos > 0) startpos--; /* this position is 1-based */

		if (hawk_rtx_getnargs(rtx) >= 4 && hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 3), &maxlen) <= -1) maxlen = HAWK_TYPE_MAX(hawk_ooi_t);
		else if (maxlen < 0) maxlen = 0;

		a1 = hawk_rtx_getarg(rtx, 1);
		dptr = hawk_rtx_getvalbcstr(rtx, a1, &dlen);
		if (dptr)
		{
			if (dlen > maxlen) dlen = maxlen;
			if (startpos >= dlen) startpos = dlen;
			dlen -= startpos;

			rx = write(sys_node->ctx.u.file.fd, &dptr[startpos], dlen);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_T("unable to write"));
			hawk_rtx_freevalbcstr(rtx, a1, dptr);
		}
		else
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}

	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */
	hawk_rtx_setretval(rtx, retv);
	return 0;
}

/* ------------------------------------------------------------------------ */

/*
	a = sys::open("/etc/inittab", sys::O_RDONLY);
	x = sys::open("/etc/fstab", sys::O_RDONLY);

	b = sys::dup(a);
	sys::close(a);

	while (sys::read(b, abc, 100) > 0) printf (@b"%s", abc);

	print "-------------------------------";

	c = sys::dup(x, b, sys::O_CLOEXEC);
	## assertion: b == c
	sys::close(x);

	while (sys::read(c, abc, 100) > 0) printf (@b"%s", abc);
	sys::close(c);
*/

static int fnc_dup (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node, * sys_node2 = HAWK_NULL;
	hawk_int_t rx;
	hawk_int_t oflags = 0;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		int fd;

		if (hawk_rtx_getnargs(rtx) >= 2)
		{
			sys_node2 = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 1), SYS_NODE_DATA_TYPE_FILE, &rx);
			if (!sys_node2) goto done;
			if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &oflags) <= -1 || oflags < 0)) oflags = 0;

			if (sys_node->ctx.u.file.fd == sys_node2->ctx.u.file.fd)
			{
				rx = set_error_on_sys_list(rtx, sys_list, HAWK_EPERM, HAWK_T("same descriptor"));
				goto done;
			}
		}

		if (sys_node2)
		{
		#if defined(HAVE_DUP3)
			fd = dup3(sys_node->ctx.u.file.fd, sys_node2->ctx.u.file.fd, oflags);
		#else
			fd = dup2(sys_node->ctx.u.file.fd, sys_node2->ctx.u.file.fd);
		#endif
			if (fd >= 0)
			{
		#if defined(HAVE_DUP3)
				/* nothing extra for dup3 */
		#else
				if (oflags)
				{
					int xflags;
				#if defined(O_CLOEXEC) && defined(FD_CLOEXEC)
					if (oflags & O_CLOEXEC)
					{
						xflags = fcntl(fd, F_GETFD);
						if (xflags >= 0) fcntl(fd, F_SETFD, xflags | FD_CLOEXEC);
					}
				#endif
				#if defined(O_NONBLOCK)
					/*if (oflags & O_NONBLOCK)
					{
						xflags = fcntl(fd, F_GETFL);
						if (xflags >= 0) fcntl(fd, F_SETFL, xflags | O_NONBLOCK);
					} dup3() doesn't seem to support NONBLOCK. */
				#endif
				}
		#endif
				/* dup2 or dup3 closes the descriptor sys_node2_.ctx.u.file.fd implicitly
				 * if it's registered in muxtipler, unregister it as well */
				del_from_mux(rtx, sys_node2);
				sys_node2->ctx.u.file.fd = fd;
				sys_node2->ctx.type = sys_node->ctx.type;
				rx = sys_node2->id;
			}
			else
			{
				rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			}
		}
		else
		{
			fd = dup(sys_node->ctx.u.file.fd);
			if (fd >= 0)
			{
				sys_node_t* new_node;

				new_node = new_sys_node_fd(rtx, sys_list, fd);
				if (new_node)
				{
					new_node->ctx.type = sys_node->ctx.type;
					rx = new_node->id;
				}
				else
				{
					close(fd);
					rx = copy_error_to_sys_list(rtx, sys_list);
				}
			}
			else
			{
				rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			}
		}
	}

done:
	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx)); /* assume a file descriptor never gets larger than HAWK_INT_MAX */
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}


static int fnc_fcntl (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_int_t  cmd;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &cmd) <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		switch (cmd)
		{
			case F_GETFD:
			case F_GETFL:
			{
				rx = fcntl(sys_node->ctx.u.file.fd, cmd, 0);
				if (rx <= -1)
				{
					set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
					goto done;
				}
				break;
			}

			case F_SETFD:
			case F_SETFL:
			{
				hawk_int_t v = 0;
				if (hawk_rtx_getnargs(rtx) >= 3 && hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &v) <= -1) goto fail;
				rx = fcntl(sys_node->ctx.u.file.fd, cmd, v);
				if (rx <= -1)
				{
					set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
					goto done;
				}
				break;
			}

			default:
				rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
				break;
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

/*
  BEGIN {
        f1 = sys::open("/tmp/x", sys::O_RDWR | sys::O_CREAT | sys::O_TRUNC, 0644);
        if (sys::flock(f1, sys::FLOCK_WRITE | sys::FLOCK_GET, 0, 0, sys::SEEK_SET, pid) != sys::FLOCK_UNLOCK) print "locked by", pid;
        sys::flock(f1, sys::FLOCK_WRITE | sys::FLOCK_WAIT, 0, 0, sys::SEEK_SET);
        sys::sleep(ARGV[1]);
        sys::flock(f1, sys::FLOCK_UNLOCK | sys::FLOCK_WAIT, 0, 0, sys::SEEK_SET);
        sys::sleep(ARGV[1]);
        sys::close(f1);
 }
 */
static int fnc_flock (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	HAWK_STATIC_ASSERT(FLOCK_GET != F_RDLCK);
	HAWK_STATIC_ASSERT(FLOCK_GET != F_WRLCK);
	HAWK_STATIC_ASSERT(FLOCK_GET != F_UNLCK);
	HAWK_STATIC_ASSERT(FLOCK_WAIT != F_RDLCK);
	HAWK_STATIC_ASSERT(FLOCK_WAIT != F_WRLCK);
	HAWK_STATIC_ASSERT(FLOCK_WAIT != F_UNLCK);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		hawk_int_t type, start, len, whence;
		int wait, get;
		struct flock fl;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &type) <= -1 ||
		    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &start) <= -1 ||
		    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 3), &len) <= -1 ||
		    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 4), &whence) <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		wait = !!(type & FLOCK_WAIT);
		get = !!(type & FLOCK_GET);
		type &= ~(FLOCK_WAIT | FLOCK_GET);

		HAWK_MEMSET (&fl, 0, HAWK_SIZEOF(fl));
		fl.l_type = type;
		fl.l_whence = whence;
		fl.l_start = start;
		fl.l_len = len;

		rx = fcntl(sys_node->ctx.u.file.fd, (get? F_GETLK: (wait? F_SETLKW: F_SETLK)), &fl);
		if (rx <= -1)
		{
			set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}

		if (get && hawk_rtx_getnargs(rtx) >= 6)
		{
			hawk_val_t* sv;
			int x;

			sv = hawk_rtx_makeintval(rtx, fl.l_pid);
			if (!sv) goto fail;

			hawk_rtx_refupval(rtx, sv);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 5), sv);
			hawk_rtx_refdownval(rtx, sv);
			if (x <= -1) goto fail;

			rx = fl.l_type; /* for get, it returns the lock type */
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_fseek (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) /* this is actually lseek */
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		hawk_int_t offset, whence;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &offset) <= -1 ||
		    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &whence) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		rx = lseek(sys_node->ctx.u.file.fd, offset, whence);
		if (rx <= -1)
		{
			set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}


/*
 BEGIN {
     if (sys::tcgetattr(sys::openfd(1), a) <= -1) print sys::errmsg();
     for (i in a) print i, a[i];
 }
*/
static int fnc_tcgetattr (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) /* this is actually lseek */
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		struct termios t;
		hawk_val_map_data_t md[5];
		hawk_bcs_t c_cc;
		hawk_val_t* tmp;
		int x;

		rx = tcgetattr(sys_node->ctx.u.file.fd, &t);
		if (rx <= -1)
		{
			set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}

		/* make a map value containg configuration */
		HAWK_MEMSET (md, 0, HAWK_SIZEOF(md));

		md[0].key.ptr = HAWK_T("iflag");
		md[0].key.len = 5;
		md[0].type = HAWK_VAL_MAP_DATA_INT;
		md[0].type_size = HAWK_SIZEOF(t.c_iflag);
		md[0].vptr = &t.c_iflag;

		md[1].key.ptr = HAWK_T("oflag");
		md[1].key.len = 5;
		md[1].type = HAWK_VAL_MAP_DATA_INT;
		md[1].type_size = HAWK_SIZEOF(t.c_oflag);
		md[1].vptr = &t.c_oflag;

		md[2].key.ptr = HAWK_T("cflag");
		md[2].key.len = 5;
		md[2].type = HAWK_VAL_MAP_DATA_INT;
		md[2].type_size = HAWK_SIZEOF(t.c_cflag);
		md[2].vptr = &t.c_cflag;

		md[3].key.ptr = HAWK_T("lflag");
		md[3].key.len = 5;
		md[3].type = HAWK_VAL_MAP_DATA_INT;
		md[3].type_size = HAWK_SIZEOF(t.c_lflag);
		md[3].vptr = &t.c_lflag;

		md[4].key.ptr = HAWK_T("cc");
		md[4].key.len = 2;
		md[4].type = HAWK_VAL_MAP_DATA_BCS;
		md[4].vptr = &c_cc;
		c_cc.ptr = (hawk_bch_t*)t.c_cc;
		c_cc.len = HAWK_COUNTOF(t.c_cc);

		tmp = hawk_rtx_makemapvalwithdata(rtx, md, HAWK_COUNTOF(md));
		if (HAWK_UNLIKELY(!tmp)) goto fail;

		hawk_rtx_refupval(rtx, tmp);
		x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), tmp);
		hawk_rtx_refdownval(rtx, tmp);
		if (x <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

/*
BEGIN {
        IN = sys::openfd(0);
        ##OUT = sys::openfd(1);
        sys::tcgetattr(IN, a);
        a["lflag"] &= ~sys::TC_LFLAG_ECHO;
        sys::tcsetattr(IN, 0, a);
        printf ("Password:");
        ##sys::write (OUT, @b"Password:");
        getline x;
        a["lflag"] |= sys::TC_LFLAG_ECHO;
        sys::tcsetattr(IN, 0, a);
        printf "\nYour input is [%s]\n", x;
}
*/
static int fnc_tcsetattr (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) /* this is actually lseek */
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		struct termios t;
		hawk_map_itr_t itr;
		hawk_map_pair_t* pair;
		hawk_val_t* a2;
		hawk_int_t action, flag;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &action) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		a2 = hawk_rtx_getarg(rtx, 2);
		if (HAWK_RTX_GETVALTYPE(rtx, a2) != HAWK_VAL_MAP)
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		/* i call this to keep the old value for the fields not present in the given attribute map */
		rx = tcgetattr(sys_node->ctx.u.file.fd, &t);
		if (rx <= -1) goto fail_with_errno;

		hawk_init_map_itr (&itr, 0);
		pair = hawk_map_getfirstpair(((hawk_val_map_t*)a2)->map, &itr);
		while (pair)
		{
			if (hawk_comp_oochars_bcstr(HAWK_MAP_KPTR(pair), HAWK_MAP_KLEN(pair), "cc", 0) == 0)
			{
				hawk_bch_t* ptr;
				hawk_oow_t len;

				ptr = hawk_rtx_getvalbcstr(rtx, HAWK_MAP_VPTR(pair), &len);
				if (!ptr)
				{
					rx = copy_error_to_sys_list(rtx, sys_list);
					goto done;
				}

				if (len >= HAWK_COUNTOF(t.c_cc)) len = HAWK_COUNTOF(t.c_cc);
				HAWK_MEMCPY (t.c_cc, ptr, len);
				hawk_rtx_freevalbcstr(rtx, HAWK_MAP_VPTR(pair), ptr);
			}
			else
			{
				if (hawk_rtx_valtoint(rtx, HAWK_MAP_VPTR(pair), &flag) <= -1)
				{
					rx = copy_error_to_sys_list(rtx, sys_list);
					goto done;
				}

				if (hawk_comp_oochars_bcstr(HAWK_MAP_KPTR(pair), HAWK_MAP_KLEN(pair), "iflag", 0) == 0)
				{
					t.c_iflag = flag;
				}
				else if (hawk_comp_oochars_bcstr(HAWK_MAP_KPTR(pair), HAWK_MAP_KLEN(pair), "oflag", 0) == 0)
				{
					t.c_oflag = flag;
				}
				else if (hawk_comp_oochars_bcstr(HAWK_MAP_KPTR(pair), HAWK_MAP_KLEN(pair), "cflag", 0) == 0)
				{
					t.c_cflag = flag;
				}
				else if (hawk_comp_oochars_bcstr(HAWK_MAP_KPTR(pair), HAWK_MAP_KLEN(pair), "lflag", 0) == 0)
				{
					t.c_lflag = flag;
				}
			}


			pair = hawk_map_getnextpair(((hawk_val_map_t*)a2)->map, &itr);
		}

		rx = tcsetattr(sys_node->ctx.u.file.fd, action, &t);
		if (rx <= -1)
		{
		fail_with_errno:
			set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

/*
BEGIN {
        IN = sys::openfd(0);
        sys::tcgetattr(IN, a);
        sys::tcsetraw(IN);
        sys::read(IN, x, 1);
        sys::tcsetattr(IN, 0, a);
        print x;
}
*/
/*TODO: tcsetsane, tcsetcooked, etc that stty supports */
static int fnc_tcsetraw (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		struct termios t;

		rx = tcgetattr(sys_node->ctx.u.file.fd, &t);
		if (rx <= -1) goto fail_with_errno;

		t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
		t.c_oflag &= ~(OPOST);
		t.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
		t.c_cflag &= ~(CSIZE | PARENB);
		t.c_cflag |= CS8;

		rx = tcsetattr(sys_node->ctx.u.file.fd, 0, &t);
		if (rx <= -1)
		{
		fail_with_errno:
			set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_tcflush (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		hawk_int_t qs;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &qs) <= -1) qs = TCIOFLUSH;

		rx = tcflush(sys_node->ctx.u.file.fd, qs);
		if (rx <= -1)
		{
			set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}
/* ------------------------------------------------------------------------ */

/*
	##if (sys::pipe(p0, p1) <= -1)
	if (sys::pipe(p0, p1, sys::O_NONBLOCK | sys::O_CLOEXEC) <= -1)
	{
		 print "pipe error";
		 return -1;
	}
	a = sys::fork();
	if (a <= -1)
	{
		print "fork error";
		sys::close(p0);
		sys::close(p1);
	}
	else if (a == 0)
	{
		## child
		printf ("child.... %d %d %d\n", sys::getpid(), p0, p1);
		sys::close(p1);
		while (1)
		{
			n = sys::read(p0, k, 3);
			if (n <= 0)
			{
				if (n == sys::RC_EAGAIN) continue; ## nonblock but data not available
				if (n != 0) print "ERROR: " sys::errmsg();
				break;
			}
			print k;
		}
		sys::close(p0);
	}
	else
	{
		## parent
		printf ("parent.... %d %d %d\n", sys::getpid(), p0, p1);
		sys::close(p0);
		sys::write(p1, @b"hello");
		sys::write(p1, @b"world");
		sys::close(p1);
		sys::wait(a);
	}##if (sys::pipe(p0, p1) <= -1)
	if (sys::pipe(p0, p1, sys::O_NONBLOCK | sys::O_CLOEXEC) <= -1)
	{
		 print "pipe error";
		 return -1;
	}
	a = sys::fork();
	if (a <= -1)
	{
		print "fork error";
		sys::close(p0);
		sys::close(p1);
	}
	else if (a == 0)
	{
		## child
		printf ("child.... %d %d %d\n", sys::getpid(), p0, p1);
		sys::close(p1);
		while (1)
		{
			n = sys::read (p0, k, 3);
			if (n <= 0)
			{
				if (n == sys::RC_EAGAIN) continue; ## nonblock but data not available
				if (n != 0) print "ERROR: " sys::errmsg();
				break;
			}
			print k;
		}
		sys::close(p0);
	}
	else
	{
		## parent
		printf ("parent.... %d %d %d\n", sys::getpid(), p0, p1);
		sys::close(p0);
		sys::write(p1, @b"hello");
		sys::write(p1, @b"world");
		sys::close(p1);
		sys::wait(a);
	}
*/

static int fnc_pipe (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	/* create low-level pipes */

	sys_list_t* sys_list;
	hawk_int_t rx;
	int fds[2];
	hawk_int_t flags = 0;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &flags) <= -1 || flags < 0)) flags = 0;

#if defined(HAVE_PIPE2)
	if (pipe2(fds, flags) >= 0)
#else
	if (pipe(fds) >= 0)
#endif
	{
		sys_node_t* node1, * node2;

	#if defined(HAVE_PIPE2)
		/* do nothing extra */
	#else
		if (flags > 0)
		{
			int xflags;

		#if defined(O_CLOEXEC) && defined(FD_CLOEXEC)
			if (flags & O_CLOEXEC)
			{
				xflags = fcntl(fds[0], F_GETFD);
				if (xflags >= 0) fcntl(fds[0], F_SETFD, xflags | FD_CLOEXEC);
				xflags = fcntl(fds[1], F_GETFD);
				if (xflags >= 0) fcntl(fds[1], F_SETFD, xflags | FD_CLOEXEC);
			}
		#endif
		#if defined(O_NONBLOCK)
			if (flags & O_NONBLOCK)
			{
				xflags = fcntl(fds[0], F_GETFL);
				if (xflags >= 0) fcntl(fds[0], F_SETFL, xflags | O_NONBLOCK);
				xflags = fcntl(fds[1], F_GETFL);
				if (xflags >= 0) fcntl(fds[1], F_SETFL, xflags | O_NONBLOCK);
			}
		#endif
		}
	#endif
		node1 = new_sys_node_fd(rtx, sys_list, fds[0]);
		node2 = new_sys_node_fd(rtx, sys_list, fds[1]);
		if (node1 && node2)
		{
			hawk_val_t* v;
			int x;

			v = hawk_rtx_makeintval(rtx, node1->id);
			if (!v) goto fail;

			hawk_rtx_refupval(rtx, v);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 0), v);
			hawk_rtx_refdownval(rtx, v);
			if (x <= -1) goto fail;

			v = hawk_rtx_makeintval(rtx, node2->id);
			if (!v) goto fail;
			hawk_rtx_refupval(rtx, v);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), v);
			hawk_rtx_refdownval(rtx, v);
			if (x <= -1) goto fail;

			/* successful so far */
			rx =  ERRNUM_TO_RC(HAWK_ENOERR);
		}
		else
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);

			if (node2) free_sys_node(rtx, sys_list, node2);
			else close(fds[1]);
			if (node1) free_sys_node(rtx, sys_list, node1);
			else close(fds[0]);
		}
	}
	else
	{
		rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------------------ */

static int fnc_fchown (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		hawk_int_t uid, gid;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &uid) <= -1 ||
		    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &gid) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
		else
		{
			rx = fchown(sys_node->ctx.u.file.fd, uid, gid) <= -1?
				set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL):
				ERRNUM_TO_RC(HAWK_ENOERR);
		}
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_fchmod (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE, &rx);
	if (sys_node)
	{
		hawk_int_t mode;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &mode) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
		else
		{
			rx = fchmod(sys_node->ctx.u.file.fd, mode) <= -1?
				set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL):
				ERRNUM_TO_RC(HAWK_ENOERR);
		}
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------------------ */

/*
	d = sys::opendir("/etc", sys::DIR_SORT);
	if (d >= 0)
	{
		while (sys::readdir(d,a) > 0) print a;
		sys::closedir(d);
	}

	#################################################

	d = sys::opendir("/tmp");
	if (d <= -1) print "opendir error", sys::errmsg();
	for (i = 0; i < 10; i++)
	{
		sys::readdir(d, a);
	       	print "[" a "]";
	}
	print "---";
	if (sys::resetdir(d, "/dev/mapper/fedora-root") <= -1)
	{
		print "reset failure:", sys::errmsg();
	}
	while (sys::readdir(d, a) > 0) print "[" a "]";
	sys::closedir(d);
*/

static int fnc_opendir (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node = HAWK_NULL;
	hawk_int_t flags = 0;
	hawk_ooch_t* pstr;
	hawk_oow_t plen;
	hawk_val_t* a0;
	hawk_dir_t* dir;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_EOTHER);

	sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_getnargs(rtx) >= 2 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &flags) <= -1 || flags < 0)) flags = 0;

	a0 = hawk_rtx_getarg(rtx, 0);
	pstr = hawk_rtx_getvaloocstr(rtx, a0, &plen);
	if (!pstr) goto fail;
	dir = hawk_dir_open(hawk_rtx_getgem(rtx), 0, pstr, flags);
	hawk_rtx_freevaloocstr(rtx, a0, pstr);

	if (dir)
	{
		sys_node = new_sys_node_dir(rtx, sys_list, dir);
		if (sys_node)
		{
			rx = sys_node->id;
		}
		else
		{
			hawk_dir_close(dir);
			goto fail;
		}
	}
	else
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
	}

	/*HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));*/
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_closedir (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_DIR, &rx);
	if (sys_node)
	{
		/* although free_sys_node() can handle other types, sys::closedir() is allowed to
		 * close nodes of the SYS_NODE_DATA_TYPE_DIR type only */
		free_sys_node(rtx, sys_list, sys_node);
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_readdir (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = 0; /* no more entry */

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_DIR, &rx);
	if (sys_node)
	{
		hawk_dir_ent_t ent;

		rx = hawk_dir_read(sys_node->ctx.u.dir, &ent); /* assume -1 on error, 0 on no more entry, 1 when an entry is available */
		if (rx <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
		else if (rx > 0)
		{
			hawk_val_t* tmp;
			int x;

			tmp = hawk_rtx_makestrvalwithoocstr(rtx, ent.name);
			if (!tmp) goto fail;

			hawk_rtx_refupval(rtx, tmp);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), tmp);
			hawk_rtx_refdownval(rtx, tmp);
			if (x <= -1) goto fail;
		}
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_resetdir (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_DIR, &rx);

	if (sys_node)
	{
		hawk_ooch_t* path;
		hawk_val_t* a1;

		a1 = hawk_rtx_getarg(rtx, 1);
		path = hawk_rtx_getvaloocstr(rtx, a1, HAWK_NULL);
		if (path)
		{
			if (hawk_dir_reset(sys_node->ctx.u.dir, path) <= -1) goto fail;
			hawk_rtx_freevaloocstr(rtx, a1, path);
		}
		else
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}

	/* no error check for hawk_rtx_makeintval() here since ret
	 * is 0 or -1. it will never fail for those numbers */
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------------------ */

static int fnc_fork (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	rx = fork();
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1; /* hard failure. unable to create a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_wait (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t pid;
	hawk_val_t* retv;
	hawk_int_t rx;
	hawk_oow_t nargs;
	hawk_int_t opts = 0;
	int status;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

	nargs = hawk_rtx_getnargs(rtx);
	if (nargs >= 3 && hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &opts) <= -1) goto fail;
	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &pid) <= -1) goto fail;

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
	status = 0;
#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
	status = 0;
#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
	status = 0;
#else
	rx = waitpid(pid, &status, opts);
	if (rx <= -1)
	{
		rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}
	else
	{
		if (nargs >= 2)
		{
			hawk_val_t* sv;
			int x;

			sv = hawk_rtx_makeintval(rtx, status);
			if (!sv) goto fail;

			hawk_rtx_refupval(rtx, sv);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), sv);
			hawk_rtx_refdownval(rtx, sv);
			if (x <= -1)
			{
			fail:
				rx = copy_error_to_sys_list(rtx, sys_list);
			}
		}
	}
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_wifexited (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t wstatus;
	int rv;
	rv = (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &wstatus) <= -1)? 0: !!WIFEXITED(wstatus);
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rv));
	return 0;
}

static int fnc_wexitstatus (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t wstatus;
	int rv;

	hawk_val_t* retv;
	rv = (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &wstatus) <= -1)? -1: WEXITSTATUS(wstatus);

	retv = hawk_rtx_makeintval(rtx, rv);
	if (!retv) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_wifsignaled (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t wstatus;
	int rv;
	rv = (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &wstatus) <= -1)? 0: !!WIFSIGNALED(wstatus);
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rv));
	return 0;
}

static int fnc_wtermsig (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t wstatus;
	int rv;

	hawk_val_t* retv;
	rv = (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &wstatus) <= -1)? -1: WTERMSIG(wstatus);

	retv = hawk_rtx_makeintval(rtx, rv);
	if (!retv) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

#if defined(WCOREDUMP)
static int fnc_wcoredump (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t wstatus;
	int rv;
	rv = hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &wstatus) <= -1? 0: !!WCOREDUMP(wstatus);
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rv));
	return 0;
}
#endif

static int fnc_kill (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t pid, sig;
	hawk_val_t* retv;
	hawk_int_t rx;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &pid) <= -1 ||
	    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &sig) <= -1)
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
	}
	else
	{
#if defined(_WIN32)
		/* TOOD: implement this*/
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
		/* TOOD: implement this*/
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
		/* TOOD: implement this*/
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
		rx = kill(pid, sig);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
#endif
	}

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_getpgid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	/* TODO: support specifing calling process id other than 0 */
	#if defined(HAVE_GETPGID)
	rx = getpgid(0);
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	#elif defined(HAVE_GETPGRP)
	rx = getpgrp();
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	#else
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
	#endif
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_getpid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	rx = GetCurrentProcessId();
	/* never fails */

#elif defined(__OS2__)
	PTIB tib;
	PPIB pib;
	APIRET rc;

	rc = DosGetInfoBlocks(&tib, &pib);
	if (rc == NO_ERROR)
	{
		rx = pib->pib_ulpid;
	}
	else
	{
		rx = set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(rc), HAWK_NULL);
	}

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	rx = getpid ();
	/* getpid() never fails */
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_gettid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_intptr_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	rx = GetCurrentThreadId();
	/* never fails */
#elif defined(__OS2__)
	PTIB tib;
	PPIB pib;
	APIRET rc;

	rc = DosGetInfoBlocks(&tib, &pib);
	if (rc == NO_ERROR)
	{
		if (tib->tib_ptib2) rx = tib->tib_ptib2->tib2_ultid;
		else rx = set_error_on_sys_list(rtx, sys_list, HAWK_ESYSERR, HAWK_NULL);
	}
	else
	{
		rx = set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(rc), HAWK_NULL);
	}

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	#if defined(SYS_gettid) && defined(HAWK_SYSCALL0)
	HAWK_SYSCALL0 (rx, SYS_gettid);
	#elif defined(SYS_gettid)
	rx = syscall(SYS_gettid);
	#else
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
	#endif
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_getppid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	DWORD pid;
	HANDLE ps;
	PROCESSENTRY32 p;

	pid = GetCurrentPorcessId();
	ps = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (ps == INVALID_HANDLE_VALUE)
	{
		rx = set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(GetLastError()), HAWK_NULL);
	}
	else
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOENT, HAWK_NULL);

		p.dwSize = HAWK_SZIEOF(p);
		if (Process32First(ps, &p))
		{
			do
			{
				if (p.th32ProcessID == pid)
				{
					rx = p.th32ParentProcessID; /* got it */
					break;
				}
			}
			while (Process32Next(ps, &p));
		}

		CloseHandle (ps);
	}

#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	rx = getppid();
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_getuid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	rx = getuid();
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_getgid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	rx = getgid();
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_geteuid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	rx = geteuid();
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_getegid (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx;
	hawk_val_t* retv;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

#if defined(_WIN32)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__OS2__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#elif defined(__DOS__)
	/* TOOD: implement this*/
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);

#else
	rx = getegid();
#endif

	retv = hawk_rtx_makeintval(rtx, rx);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int val_to_ntime (hawk_rtx_t* rtx, hawk_val_t* val, hawk_ntime_t* nt)
{
	hawk_int_t lv;
	hawk_flt_t fv;
	int x;

	x = hawk_rtx_valtonum(rtx, val, &lv, &fv);
	if (x == 0)
	{
		nt->sec = lv;
		nt->nsec = 0;
	}
	else if (x >= 1)
	{
		nt->sec = (hawk_int_t)fv;
		nt->nsec = HAWK_SEC_TO_NSEC(fv - nt->sec);
	}

	return x;
}

static int fnc_sleep (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_ntime_t nt;
	hawk_val_t* retv;
	hawk_int_t rx;
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

	rx = val_to_ntime(rtx, hawk_rtx_getarg(rtx, 0), &nt);
	if (rx <= -1)
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

#if defined(_WIN32)
	Sleep (HAWK_SECNSEC_TO_MSEC(nt.sec, nt.nsec));
	rx = 0;
#elif defined(__OS2__)
	DosSleep ((ULONG)HAWK_SECNSEC_TO_MSEC(nt.sec, nt.nsec)));
	rx = 0;
#elif defined(__DOS__)
	/* no high-resolution sleep() is available */
	#if (defined(__WATCOMC__) && (__WATCOMC__ < 1200))
	sleep (nt.sec);
	rx = 0;
	#else
	rx = sleep(nt.sec);
	#endif
#elif defined(HAVE_NANOSLEEP)
	{
		struct timespec req;
		req.tv_sec = nt.sec;
		req.tv_nsec = nt.nsec;
		rx = nanosleep(&req, HAWK_NULL);
	}
#elif defined(HAVE_SELECT)
	{
		struct timeval req;
		req.tv_sec = nt.sec;
		req.tv_usec = HAWK_NSEC_TO_USEC(nt.nsec);
		rx = select(0, HAWK_NULL, HAWK_NULL, HAWK_NULL, &req);
	}
#else
	/* no high-resolution sleep() is available */
	rx = sleep(nt.sec);
#endif

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_gettime (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_val_t* retv;
	hawk_ntime_t now;

	/*
		sec = sys::gettime());
		sec = sys::gettime(&nanosec));
	*/

	if (hawk_get_ntime(&now) <= -1) now.sec = 0;

	if (hawk_rtx_getnargs(rtx) >= 1)
	{
		hawk_val_t* tmp;
		int x;

		tmp = hawk_rtx_makeintval(rtx, now.nsec);
		if (HAWK_UNLIKELY(!tmp)) return -1;

		hawk_rtx_refupval(rtx, tmp);
		x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 0), tmp);
		hawk_rtx_refdownval(rtx, tmp);
		if (HAWK_UNLIKELY(x <= -1)) return -1;
	}

	retv = hawk_rtx_makeintval(rtx, now.sec);
	if (HAWK_UNLIKELY(!retv)) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_settime (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_val_t* retv;
	hawk_ntime_t now;
	hawk_int_t tmp;
	hawk_int_t rx;

	now.nsec = 0;

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &tmp) <= -1) rx = -1;
	else
	{
		now.sec = tmp;
		if (hawk_set_ntime(&now) <= -1) rx = -1;
		else rx = 0;
	}

	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}

static int fnc_mktime (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_ntime_t nt;
	hawk_oow_t nargs;
	hawk_val_t* retv;

	nargs = hawk_rtx_getnargs(rtx);
	if (nargs >= 1)
	{
		int sign;
		hawk_ooch_t* str, * p, * end;
		hawk_oow_t len;
		hawk_val_t* a0;
		struct tm tm;

		a0 = hawk_rtx_getarg(rtx, 0);
		str = hawk_rtx_getvaloocstr(rtx, a0, &len);
		if (str == HAWK_NULL) return -1;

		/* the string must be of the format  YYYY MM DD HH MM SS[ DST] */
		p = str;
		end = str + len;
		HAWK_MEMSET (&tm, 0, HAWK_SIZEOF(tm));

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_year = tm.tm_year * 10 + (*p++ - '0');
		tm.tm_year *= sign;
		tm.tm_year -= 1900;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_mon = tm.tm_mon * 10 + (*p++ - '0');
		tm.tm_mon *= sign;
		tm.tm_mon -= 1;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_mday = tm.tm_mday * 10 + (*p++ - '0');
		tm.tm_mday *= sign;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_hour = tm.tm_hour * 10 + (*p++ - '0');
		tm.tm_hour *= sign;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_min = tm.tm_min * 10 + (*p++ - '0');
		tm.tm_min *= sign;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_sec = tm.tm_sec * 10 + (*p++ - '0');
		tm.tm_sec *= sign;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		sign = 1;
		if (p < end && *p == '-') { sign = -1; p++; }
		while (p < end && hawk_is_ooch_digit(*p)) tm.tm_isdst = tm.tm_isdst * 10 + (*p++ - '0');
		tm.tm_isdst *= sign;
		while (p < end && (hawk_is_ooch_space(*p) || *p == '\0')) p++;

		hawk_rtx_freevaloocstr(rtx, a0, str);
	#if defined(HAVE_TIMELOCAL)
		nt.sec = timelocal(&tm);
	#else
		nt.sec = mktime(&tm);
	#endif
	}
	else
	{
		/* get the current time when no argument is given */
		hawk_get_ntime (&nt);
	}

	retv = hawk_rtx_makeintval(rtx, nt.sec);
	if (retv == HAWK_NULL) return -1;

	hawk_rtx_setretval(rtx, retv);
	return 0;
}


#define STRFTIME_UTC (1 << 0)

static int fnc_strftime (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{

	/*
	sys::strftime("%Y-%m-%d %H:%M:%S %z", sys::gettime());
	sys::strftime("%Y-%m-%d %H:%M:%S %z", sys::gettime(), sys::STRFTIME_UTC);
	*/

	hawk_bch_t* fmt;
	hawk_oow_t len;
	hawk_val_t* retv;

	fmt = hawk_rtx_valtobcstrdup(rtx, hawk_rtx_getarg(rtx, 0), &len);
	if (fmt)
	{
		hawk_ntime_t nt;
		struct tm tm, * tmx;
		hawk_int_t tmpsec, flags = 0;

		nt.nsec = 0;
		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &tmpsec) <= -1)
		{
			nt.sec = 0;
		}
		else
		{
			nt.sec = tmpsec;
		}

		if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &flags) <= -1 || flags < 0)) flags = 0;

		if (flags & STRFTIME_UTC)
		{
			time_t t = nt.sec;
		#if defined(HAVE_GMTIME_R)
			tmx = gmtime_r(&t, &tm);
		#else
			tmx = gmtime(&t.sec);
		#endif
		}
		else
		{
			time_t t = nt.sec;
		#if defined(HAVE_LOCALTIME_R)
			tmx = localtime_r(&t, &tm);
		#else
			tmx = localtime(&t.sec);
		#endif
		}

		if (tmx)
		{
			hawk_bch_t tmpbuf[64], * tmpptr;
			hawk_oow_t sl;

#if 0
			if (flags & STRFTIME_UTC)
			{
			#if defined(HAVE_STRUCT_TM_TM_ZONE)
				tm.tm_zone = "GMT";
			#elif defined(HAVE_STRUCT_TM___TM_ZONE)
				tm.__tm_zone = "GMT";
			#endif
			}
#endif

			sl = strftime(tmpbuf, HAWK_COUNTOF(tmpbuf), fmt, tmx);
			if (sl <= 0 || sl >= HAWK_COUNTOF(tmpbuf))
			{
				/* buffer too small */
				hawk_bch_t* tmp;
				hawk_oow_t tmpcapa, i, count = 0;

/*
man strftime >>>

RETURN VALUE
       The strftime() function returns the number of bytes placed in the array s, not including the  terminating  null  byte,  provided  the
       string,  including  the  terminating  null  byte,  fits.  Otherwise, it returns 0, and the contents of the array is undefined.  (This
       behavior applies since at least libc 4.4.4; very old versions of libc, such as libc 4.4.1, would return max  if  the  array  was  too
       small.)

       Note that the return value 0 does not necessarily indicate an error; for example, in many locales %p yields an empty string.

--------------------------------------------------------------------------------------
*
I use 'count' to limit the maximum number of retries when 0 is returned.
*/

				for (i = 0; i < len;)
				{
					if (fmt[i] == HAWK_BT('%'))
					{
						count++; /* the nubmer of % specifier */
						i++;
						if (i < len) i++;
					}
					else i++;
				}

				tmpptr = HAWK_NULL;
				tmpcapa = HAWK_COUNTOF(tmpbuf);
				if (tmpcapa < len) tmpcapa = len;

				do
				{
					if (count <= 0)
					{
						if (tmpptr) hawk_rtx_freemem(rtx, tmpptr);
						tmpbuf[0] = HAWK_BT('\0');
						tmpptr = tmpbuf;
						break;
					}
					count--;

					tmpcapa *= 2;
					tmp = (hawk_bch_t*)hawk_rtx_reallocmem(rtx, tmpptr, tmpcapa * HAWK_SIZEOF(*tmpptr));
					if (!tmp)
					{
						if (tmpptr) hawk_rtx_freemem(rtx, tmpptr);
						tmpbuf[0] = HAWK_BT('\0');
						tmpptr = tmpbuf;
						break;
					}

					tmpptr = tmp;
					sl = strftime(tmpptr, tmpcapa, fmt, &tm);
				}
				while (sl <= 0 || sl >= tmpcapa);
			}
			else
			{
				tmpptr = tmpbuf;
			}
			hawk_rtx_freemem(rtx, fmt);

			retv = hawk_rtx_makestrvalwithbcstr(rtx, tmpptr);
			if (tmpptr && tmpptr != tmpbuf) hawk_rtx_freemem(rtx, tmpptr);
			if (retv == HAWK_NULL) return -1;

			hawk_rtx_setretval(rtx, retv);
		}
		else
		{
			hawk_rtx_freemem(rtx, fmt);
		}
	}

	return 0;
}

/*
	if (sys::getenv("PATH", v) <= -1) print "error -", sys::errmsg();
	else print v;
*/
static int fnc_getenv (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_bch_t* var;
	hawk_oow_t len;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);

	a0 = hawk_rtx_getarg(rtx, 0);
	var = hawk_rtx_getvalbcstr(rtx, a0, &len);
	if (var)
	{
		hawk_bch_t* val;

		if (hawk_find_bchar_in_bchars(var, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			hawk_rtx_freevalbcstr(rtx, a0, var);
		}
		else
		{
			val = getenv(var);
			hawk_rtx_freevalbcstr(rtx, a0, var);

			if (val)
			{
				hawk_val_t* tmp;
				int x;

				tmp = hawk_rtx_makestrvalwithbcstr(rtx, val);
				if (!tmp) goto fail;

				hawk_rtx_refupval(rtx, tmp);
				x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), tmp);
				hawk_rtx_refdownval(rtx, tmp);
				if (x <= -1) goto fail;

				rx = ERRNUM_TO_RC(HAWK_ENOERR);
			}
			else
			{
				rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOENT, HAWK_NULL);
			}
		}
	}
	else
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_setenv (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0, * a1;
	hawk_bch_t* var = HAWK_NULL, * val = HAWK_NULL;
	hawk_oow_t var_len, val_len;
	hawk_int_t rx;
	hawk_int_t overwrite = 1;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);
	a1 = hawk_rtx_getarg(rtx, 1);

	var = hawk_rtx_getvalbcstr(rtx, a0, &var_len);
	val = hawk_rtx_getvalbcstr(rtx, a1, &val_len);
	if (!var || !val)
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

	/* the target name contains a null character. */
	if (hawk_find_bchar_in_bchars(var, var_len, '\0') ||
	    hawk_find_bchar_in_bchars(val, val_len, '\0'))
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
		goto done;
	}

	if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &overwrite) <= -1)) overwrite = 0;

	rx = setenv(var, val, overwrite);
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);

done:
	if (val) hawk_rtx_freevalbcstr(rtx, a1, val);
	if (var) hawk_rtx_freevalbcstr(rtx, a0, var);

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}


static int fnc_unsetenv (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_bch_t* str;
	hawk_oow_t len;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

	str = hawk_rtx_getvalbcstr(rtx, a0, &len);
	if (!str)
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

	/* the target name contains a null character. */
	if (hawk_find_bchar_in_bchars(str, len, '\0'))
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
		goto done;
	}

#if defined(UNSETENV_RETURNS_VOID)
	unsetenv(str);
	rx = 0;
#else
	rx = unsetenv(str);
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
#endif

done:
	if (str) hawk_rtx_freevalbcstr(rtx, a0, str);

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------ */

/*
BEGIN {
	if (sys::getnwifcfg("eth0", sys::NWIFCFG_IN6, x) >= 0)
	{
	    for (i in x) print i, x[i];
	}
	else
	{
	    print "Error:", sys::errmsg();
	}
}
*/
static int fnc_getifcfg (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_ifcfg_t cfg;
	hawk_rtx_valtostr_out_t out;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);

	HAWK_MEMSET (&cfg, 0, HAWK_SIZEOF(cfg));

	out.type = HAWK_RTX_VALTOSTR_CPLCPY;
	out.u.cplcpy.ptr = cfg.name;
	out.u.cplcpy.len = HAWK_COUNTOF(cfg.name);
	if (hawk_rtx_valtostr(rtx, hawk_rtx_getarg(rtx, 0), &out) >= 0)
	{
		hawk_int_t type;
		hawk_int_t index, mtu;
		hawk_ooch_t ethw[32];
		hawk_val_map_data_t md[6];
		hawk_oow_t md_count;
		hawk_val_t* tmp;
		int x;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &type) <= -1) goto fail;

		cfg.type = type;
		if (hawk_gem_getifcfg(hawk_rtx_getgem(rtx), &cfg) <= -1) goto fail;

		/* make a map value containg configuration */
		HAWK_MEMSET (md, 0, HAWK_SIZEOF(md));

		md[0].key.ptr = HAWK_T("index");
		md[0].key.len = 5;
		md[0].type = HAWK_VAL_MAP_DATA_INT;
		md[0].type_size = HAWK_SIZEOF(cfg.index);
		md[0].vptr = &cfg.index;

		md[1].key.ptr = HAWK_T("mtu");
		md[1].key.len = 3;
		md[1].type = HAWK_VAL_MAP_DATA_INT;
		md[1].type_size = HAWK_SIZEOF(cfg.mtu);
		md[1].vptr = &cfg.mtu;

		md[2].key.ptr = HAWK_T("addr");
		md[2].key.len = 4;
		md[2].type = HAWK_VAL_MAP_DATA_OOCSTR;
		hawk_gem_skadtooocstr (hawk_rtx_getgem(rtx), &cfg.addr, sys_list->ctx.skadbuf[0], HAWK_COUNTOF(sys_list->ctx.skadbuf[0]), HAWK_SKAD_TO_OOCSTR_ADDR);
		md[2].vptr = sys_list->ctx.skadbuf[0];

		md[3].key.ptr = HAWK_T("mask");
		md[3].key.len = 4;
		md[3].type = HAWK_VAL_MAP_DATA_OOCSTR;
		hawk_gem_skadtooocstr (hawk_rtx_getgem(rtx), &cfg.mask, sys_list->ctx.skadbuf[1], HAWK_COUNTOF(sys_list->ctx.skadbuf[1]), HAWK_SKAD_TO_OOCSTR_ADDR);
		md[3].vptr = sys_list->ctx.skadbuf[1];

		md[4].key.ptr = HAWK_T("ethw");
		md[4].key.len = 4;
		md[4].type = HAWK_VAL_MAP_DATA_OOCSTR;
		hawk_rtx_fmttooocstr(rtx, ethw, HAWK_COUNTOF(ethw), HAWK_T("%02X:%02X:%02X:%02X:%02X:%02X"),
			cfg.ethw[0], cfg.ethw[1], cfg.ethw[2], cfg.ethw[3], cfg.ethw[4], cfg.ethw[5]);
		md[4].vptr = ethw;
		md_count = 5;

		if (cfg.flags & (HAWK_IFCFG_LINKUP | HAWK_IFCFG_LINKDOWN))
		{
			md[5].key.ptr = HAWK_T("link");
			md[5].key.len = 4;
			md[5].type = HAWK_VAL_MAP_DATA_OOCSTR;
			md[5].vptr = (cfg.flags & HAWK_IFCFG_LINKUP)? HAWK_T("up"): HAWK_T("down");
			md_count++;
		}

		tmp = hawk_rtx_makemapvalwithdata(rtx, md, md_count);
		if (!tmp) goto fail;

		hawk_rtx_refupval(rtx, tmp);
		x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 2), tmp);
		hawk_rtx_refdownval(rtx, tmp);
		if (x <= -1) goto fail;

		rx = ERRNUM_TO_RC(HAWK_ENOERR);
	}
	else
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
	}

	/* no error check for hawk_rtx_makeintval() since ret is 0 or -1 */
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}
/* ------------------------------------------------------------ */

static int fnc_system (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_ooch_t* str;
	hawk_oow_t len;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);

	a0 = hawk_rtx_getarg(rtx, 0);
	str = hawk_rtx_getvaloocstr(rtx, a0, &len);
	if (!str)
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

	/* the target name contains a null character. make system return -1 */
	if (hawk_find_oochar_in_oochars(str, len, '\0'))
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
		goto done;
	}

#if defined(_WIN32)
	rx = _tsystem(str);
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
#elif defined(HAWK_OOCH_IS_BCH)
	rx = system(str);
	if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
#else

	{
		hawk_bch_t* mbs;
		mbs = hawk_rtx_duputobcstr(rtx, str, HAWK_NULL);
		if (mbs)
		{
			rx = system(mbs);
			hawk_rtx_freemem(rtx, mbs);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		}
		else
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}

#endif

done:
	if (str) hawk_rtx_freevaloocstr(rtx, a0, str);

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------ */

static int fnc_chroot (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

#if defined(_WIN32)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	{
		hawk_bch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvalbcstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_bchar_in_bchars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		rx = HAWK_CHROOT(str);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);

	done:
		if (str) hawk_rtx_freevalbcstr(rtx, a0, str);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------ */

static int fnc_chmod (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;
	hawk_int_t mode = DEFAULT_MODE;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

	if (hawk_rtx_getnargs(rtx) >= 2 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &mode) <= -1 || mode < 0)) mode = DEFAULT_MODE;

#if defined(_WIN32)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	{
		hawk_bch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvalbcstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_bchar_in_bchars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		rx = HAWK_CHMOD(str, mode);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);

	done:
		if (str) hawk_rtx_freevalbcstr(rtx, a0, str);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}


static int fnc_mkdir (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;
	hawk_int_t mode = DEFAULT_MODE;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

	if (hawk_rtx_getnargs(rtx) >= 2 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &mode) <= -1 || mode < 0)) mode = DEFAULT_MODE;

#if defined(_WIN32)
	{
		hawk_ooch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvaloocstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_oochar_in_oochars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		rx = (CreateDirectory(str, HAWK_NULL) == FALSE)? set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(GetLastError()), HAWK_NULL): 0;

	done:
		if (str) hawk_rtx_freevaloocstr(rtx, a0, str);
	}
#else
	{
		hawk_bch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvalbcstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_bchar_in_bchars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

	#if defined(__OS2__)
		{
			APIRET rc;
			rc = DosCreateDir(str, HAWK_NULL);
			rx = (rc == NO_ERROR)? 0: set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(rc), HAWK_NULL);
		}
	#elif defined(__DOS__)
		rx = mkdir(str);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	#else
		rx = HAWK_MKDIR(str, mode);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	#endif

	done:
		if (str) hawk_rtx_freevalbcstr(rtx, a0, str);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_rmdir (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

#if defined(_WIN32)
	{
		hawk_ooch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvaloocstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_oochar_in_oochars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		rx = (RemoveDirectory(str) == FALSE)? set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(GetLastError()), HAWK_NULL): 0;

	done:
		if (str) hawk_rtx_freevaloocstr(rtx, a0, str);
	}
#else
	{
		hawk_bch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvalbcstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_bchar_in_bchars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		#if defined(__OS2__)
		{
			APIRET rc;
			rc = DosDeleteDir(str);
			rx = (rc == NO_ERROR)? 0: set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(rc), HAWK_NULL);
		}
		#elif defined(__DOS__)
			rx = rmdir(str);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		#else
			rx = HAWK_RMDIR(str);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		#endif

	done:
		if (str) hawk_rtx_freevalbcstr(rtx, a0, str);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------ */

static int fnc_unlink (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

#if defined(_WIN32)
	{
		hawk_ooch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvaloocstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_oochar_in_oochars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		rx = (DeleteFile(str) == FALSE)? set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(GetLastError()), HAWK_NULL): 0;

	done:
		if (str) hawk_rtx_freevaloocstr(rtx, a0, str);
	}
#else
	{
		hawk_bch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvalbcstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_bchar_in_bchars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		#if defined(__OS2__)
		{
			APIRET rc;
			rc = DosDelete(str, HAWK_NULL);
			rx = (rc == NO_ERROR)? 0: set_error_on_sys_list(rtx, sys_list, hawk_syserr_to_errnum(rc), HAWK_NULL);
		}
		#elif defined(__DOS__)
			rx = unlink(str);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		#else
			rx = HAWK_UNLINK(str);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		#endif

	done:
		if (str) hawk_rtx_freevalbcstr(rtx, a0, str);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_symlink (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0, * a1;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);
	a1 = hawk_rtx_getarg(rtx, 1);

#if defined(_WIN32)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	{
		hawk_bch_t* str1, * str2;
		hawk_oow_t len1, len2;

		str1 = hawk_rtx_getvalbcstr(rtx, a0, &len1);
		str2 = hawk_rtx_getvalbcstr(rtx, a1, &len2);
		if (!str1 || !str2)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		if (hawk_find_bchar_in_bchars(str1, len1, '\0') || hawk_find_bchar_in_bchars(str2, len2, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		rx = HAWK_SYMLINK(str1, str2);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	done:
		if (str2) hawk_rtx_freevalbcstr(rtx, a0, str2);
		if (str1) hawk_rtx_freevalbcstr(rtx, a0, str1);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------ */
static int fnc_stat (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

#if defined(_WIN32)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	{
		hawk_bch_t* str1;
		hawk_oow_t len1;
		hawk_val_t* tmp;
		hawk_stat_t stbuf;
		hawk_val_map_data_t md[13];
		hawk_flt_t atime_f, mtime_f, ctime_f;
		int x;

		str1 = hawk_rtx_getvalbcstr(rtx, a0, &len1);
		if (!str1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		if (hawk_find_bchar_in_bchars(str1, len1, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			hawk_rtx_freevalbcstr(rtx, a0, str1);
			goto done;
		}

		rx = HAWK_STAT(str1, &stbuf);
		hawk_rtx_freevalbcstr(rtx, a0, str1);
		if (rx <= -1)
		{
			rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}

		HAWK_MEMSET (md, 0, HAWK_SIZEOF(md));

		md[0].key.ptr = HAWK_T("dev");
		md[0].key.len = 3;
		md[0].type = HAWK_VAL_MAP_DATA_INT;
		md[0].type_size = HAWK_SIZEOF(stbuf.st_dev);
		md[0].vptr = &stbuf.st_dev;

		md[1].key.ptr = HAWK_T("ino");
		md[1].key.len = 3;
		md[1].type = HAWK_VAL_MAP_DATA_INT;
		md[1].type_size = HAWK_SIZEOF(stbuf.st_ino);
		md[1].vptr = &stbuf.st_ino;

		md[2].key.ptr = HAWK_T("size");
		md[2].key.len = 4;
		md[2].type = HAWK_VAL_MAP_DATA_INT;
		md[2].type_size = HAWK_SIZEOF(stbuf.st_size);
		md[2].vptr = &stbuf.st_size;

		md[3].key.ptr = HAWK_T("mode");
		md[3].key.len = 4;
		md[3].type = HAWK_VAL_MAP_DATA_INT;
		md[3].type_size = HAWK_SIZEOF(stbuf.st_mode);
		md[3].vptr = &stbuf.st_mode;

		md[4].key.ptr = HAWK_T("nlink");
		md[4].key.len = 5;
		md[4].type = HAWK_VAL_MAP_DATA_INT;
		md[4].type_size = HAWK_SIZEOF(stbuf.st_nlink);
		md[4].vptr = &stbuf.st_nlink;

		md[5].key.ptr = HAWK_T("uid");
		md[5].key.len = 3;
		md[5].type = HAWK_VAL_MAP_DATA_INT;
		md[5].type_size = HAWK_SIZEOF(stbuf.st_uid);
		md[5].vptr = &stbuf.st_uid;

		md[6].key.ptr = HAWK_T("gid");
		md[6].key.len = 3;
		md[6].type = HAWK_VAL_MAP_DATA_INT;
		md[6].type_size = HAWK_SIZEOF(stbuf.st_gid);
		md[6].vptr = &stbuf.st_gid;

		md[7].key.ptr = HAWK_T("rdev");
		md[7].key.len = 4;
		md[7].type = HAWK_VAL_MAP_DATA_INT;
		md[7].type_size = HAWK_SIZEOF(stbuf.st_rdev);
		md[7].vptr = &stbuf.st_rdev;

		md[8].key.ptr = HAWK_T("blksize");
		md[8].key.len = 7;
		md[8].type = HAWK_VAL_MAP_DATA_INT;
		md[8].type_size = HAWK_SIZEOF(stbuf.st_blksize);
		md[8].vptr = &stbuf.st_blksize;

		md[9].key.ptr = HAWK_T("blocks");
		md[9].key.len = 6;
		md[9].type = HAWK_VAL_MAP_DATA_INT;
		md[9].type_size = HAWK_SIZEOF(stbuf.st_blocks);
		md[9].vptr = &stbuf.st_blocks;

		md[10].key.ptr = HAWK_T("atime");
		md[10].key.len = 5;
	#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
		md[10].type = HAWK_VAL_MAP_DATA_FLT;
		atime_f = (hawk_flt_t)stbuf.st_atim.tv_sec + ((hawk_flt_t)stbuf.st_atim.tv_nsec / HAWK_NSECS_PER_SEC);
		md[10].vptr = &atime_f;
	#else
		md[10].type = HAWK_VAL_MAP_DATA_INT;
		md[10].type_size = HAWK_SIZEOF(stbuf.st_atime);
		md[10].vptr = &stbuf.st_atime;
	#endif

		md[11].key.ptr = HAWK_T("mtime");
		md[11].key.len = 5;
	#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
		md[11].type = HAWK_VAL_MAP_DATA_FLT;
		mtime_f = (hawk_flt_t)stbuf.st_mtim.tv_sec + ((hawk_flt_t)stbuf.st_mtim.tv_nsec / HAWK_NSECS_PER_SEC);
		md[11].vptr = &mtime_f;
	#else
		md[11].type = HAWK_VAL_MAP_DATA_INT;
		md[11].type_size = HAWK_SIZEOF(stbuf.st_mtime);
		md[11].vptr = &stbuf.st_mtime;
	#endif

		md[12].key.ptr = HAWK_T("ctime");
		md[12].key.len = 5;
	#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
		md[12].type = HAWK_VAL_MAP_DATA_FLT;
		ctime_f = (hawk_flt_t)stbuf.st_ctim.tv_sec + ((hawk_flt_t)stbuf.st_ctim.tv_nsec / HAWK_NSECS_PER_SEC);
		md[12].vptr = &ctime_f;
	#else
		md[12].type = HAWK_VAL_MAP_DATA_INT;
		md[12].type_size = HAWK_SIZEOF(stbuf.st_ctime);
		md[12].vptr = &stbuf.st_ctime;
	#endif

		tmp = hawk_rtx_makemapvalwithdata(rtx, md, HAWK_COUNTOF(md));
		if (!tmp) goto fail;

		hawk_rtx_refupval(rtx, tmp);
		x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), tmp);
		hawk_rtx_refdownval(rtx, tmp);
		if (x <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
		else
		{
			rx = ERRNUM_TO_RC(HAWK_ENOERR);
		}
	}
done:
#endif
	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

#if 0
TODO: fnc_utime
static int fnc_utime (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_int_t rx;
	hawk_int_t mode = DEFAULT_MODE;

	sys_list = rtx_to_sys_list(rtx, fi);
	a0 = hawk_rtx_getarg(rtx, 0);

	if (hawk_rtx_getnargs(rtx) >= 2 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &mode) <= -1 || mode < 0)) mode = DEFAULT_MODE;

#if defined(_WIN32)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__OS2__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#elif defined(__DOS__)
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#else
	{
		hawk_bch_t* str;
		hawk_oow_t len;

		str = hawk_rtx_getvalbcstr(rtx, a0, &len);
		if (!str)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		/* the target name contains a null character. */
		if (hawk_find_bchar_in_bchars(str, len, '\0'))
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

	#if defined(HAVE_UTIMENSAT)
		/* TODO: */
	#else
		{
			struct timeval tv[2];

			tv[0].tv_sec = xxx;
			tv[1].tv_sec = xxx;
			tv[1].tv_sec = xxx;
			tv[1].tv_sec = xxx;

			rx = utimes(str, tv);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		}
	#endif

	done:
		if (str) hawk_rtx_freevalbcstr(rtx, a0, str);
	}
#endif

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}
#endif

/* ------------------------------------------------------------ */

static int fnc_openmux (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node = HAWK_NULL;
	hawk_int_t rx;
	int fd;

	sys_list = rtx_to_sys_list(rtx, fi);

#if defined(USE_EPOLL)
	#if defined(HAVE_EPOLL_CREATE1) && defined(EPOLL_CLOEXEC)
	fd = epoll_create1(EPOLL_CLOEXEC);
	#else
	fd = epoll_create(4096);
	#endif
	if (fd >= 0)
	{
		#if defined(HAVE_EPOLL_CREATE1) && defined(EPOLL_CLOEXEC)
		/* nothing to do */
		#elif defined(FD_CLOEXEC)
		{
			int flag = fcntl(fd, F_GETFD);
			if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC);
		}
		#endif
	}

	if (fd >= 0)
	{
		sys_node = new_sys_node_mux(rtx, sys_list, fd);
		if (sys_node)
		{
			rx = sys_node->id;
		}
		else
		{
			close(fd);
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}
	else
	{
		rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}
#else
	rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOIMPL, HAWK_NULL);
#endif

	/*HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));*/
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_closemux (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_MUX, &rx);

	if (sys_node) free_sys_node(rtx, sys_list, sys_node);

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

#if defined(USE_EPOLL)
static HAWK_INLINE int ctl_epoll_for_fnc (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi, mux_ctl_cmd_t cmd)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node, * sys_node2;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_MUX, &rx);

	if (sys_node)
	{
		sys_node2 = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 1), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);
		if (sys_node2)
		{
			struct epoll_event ev;
			int evfd;

			if (cmd == MUX_CTL_DEL)
			{
				if (!(sys_node2->ctx.flags & SYS_NODE_DATA_FLAG_IN_MUX))
				{
					rx = set_error_on_sys_list(rtx, sys_list, HAWK_EPERM, HAWK_T("not in mux"));
					goto done;
				}
			}
			else
			{
				hawk_int_t events;

				if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &events) <= -1)
				{
					rx = copy_error_to_sys_list(rtx, sys_list);
					goto done;
				}

				ev.data.ptr = sys_node2;
				ev.events = events;
				ev.events &= ~EPOLLET; /* disable edge trigger if set */

				if (cmd == MUX_CTL_MOD)
				{
					if (!(sys_node2->ctx.flags & SYS_NODE_DATA_FLAG_IN_MUX) || sys_node2->ctx.u.file.mux != sys_node)
					{
						/* not in the multiplxer or not in the right multiplexer */
						rx = set_error_on_sys_list(rtx, sys_list, HAWK_EPERM, HAWK_T("not in mux"));
						goto done;
					}
				}
				else
				{
					if (sys_node2->ctx.flags & SYS_NODE_DATA_FLAG_IN_MUX)
					{
						/* in actual operating systems, a file descriptor can
						 * be watched under multiple I/O multiplexers. but this
						 * implementation only allows 1 multiplexer for each
						 * file descriptor for simplicity */
						rx = set_error_on_sys_list(rtx, sys_list, HAWK_EPERM, HAWK_T("already in mux"));
						goto done;
					}
				}
			}

			switch (sys_node2->ctx.type)
			{
				case SYS_NODE_DATA_TYPE_FILE:
				case SYS_NODE_DATA_TYPE_SCK:
					evfd = sys_node2->ctx.u.file.fd;
					break;

				default:
					/* internal error */
					rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINTERN, HAWK_NULL);
					goto done;
			}

			if (epoll_ctl(sys_node->ctx.u.mux.fd, cmd, evfd, &ev) <= -1)
			{
				rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			}
			else
			{
				switch (sys_node2->ctx.type)
				{
					case SYS_NODE_DATA_TYPE_FILE:
					case SYS_NODE_DATA_TYPE_SCK:
						switch (cmd)
						{
							case MUX_CTL_ADD:
								chain_sys_node_to_mux_node (sys_node, sys_node2);
								break;
							case MUX_CTL_DEL:
								nullify_mux_data (&sys_node->ctx.u.mux, sys_node2->id);
								unchain_sys_node_from_mux_node(sys_node, sys_node2);
								break;

							case MUX_CTL_MOD:
								break;
						}
						break;

					default:
						/* internal error */
						rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINTERN, HAWK_NULL);
						goto done;
				}

			}
		}
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}
#endif

static int fnc_addtomux (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
#if defined(USE_EPOLL)
	return ctl_epoll_for_fnc(rtx, fi, MUX_CTL_ADD);
#else
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, ERRNUM_TO_RC(HAWK_ENOIMPL)));
	return 0;
#endif
}

static int fnc_delfrommux (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
#if defined(USE_EPOLL)
	return ctl_epoll_for_fnc(rtx, fi, MUX_CTL_DEL);
#else
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, ERRNUM_TO_RC(HAWK_ENOIMPL)));
	return 0;
#endif
}

static int fnc_modinmux (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
#if defined(USE_EPOLL)
	return ctl_epoll_for_fnc(rtx, fi, MUX_CTL_MOD);
#else
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, ERRNUM_TO_RC(HAWK_ENOIMPL)));
	return 0;
#endif
}

static int fnc_waitonmux (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
#if defined(USE_EPOLL)
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_MUX, &rx);

	if (sys_node)
	{
		sys_node_data_mux_t* mux_data = &sys_node->ctx.u.mux;
		hawk_ntime_t tmout;

		if (val_to_ntime(rtx, hawk_rtx_getarg(rtx, 1), &tmout) <= -1 || tmout.sec <= -1) { tmout.sec = 0; tmout.nsec = HAWK_MSEC_TO_NSEC(-1); }

		if (mux_data->x_evt_max < mux_data->x_count)
		{
			struct epoll_event* tmp;

			tmp = hawk_rtx_reallocmem(rtx, mux_data->x_evt, HAWK_SIZEOF(*tmp) * HAWK_ALIGN(mux_data->x_count, 64));
			if (!tmp)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}

			mux_data->x_evt_max = HAWK_ALIGN(mux_data->x_count, 64);
			mux_data->x_evt = tmp;
		}

		/* once this function is called, invalid the exising event data regardless of success or failure */
		mux_data->x_evt_count = 0;

		if ((rx = epoll_wait(sys_node->ctx.u.mux.fd, mux_data->x_evt, mux_data->x_evt_max, HAWK_SECNSEC_TO_MSEC(tmout.sec, tmout.nsec))) <= -1)
		{
			rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
		}
		else
		{
			/* 0 on timeout, >0 if a file descriptor is ready */
			mux_data->x_evt_count = rx;
		}
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
#else
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, ERRNUM_TO_RC(HAWK_ENOIMPL)));
	return 0;
#endif
}

static int fnc_getmuxevt (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
#if defined(USE_EPOLL)
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_ENOERR);

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_MUX, &rx);

	if (sys_node)
	{
		sys_node_data_mux_t* mux_data = &sys_node->ctx.u.mux;
		sys_node_t* file_node;
		hawk_int_t index, id, evts;
		int x;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &index) <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		if (index < 0 || index >= mux_data->x_evt_count)
		{
			/* invalid index */
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
			goto done;
		}

		file_node = mux_data->x_evt[index].data.ptr;
		if (!file_node)
		{
			/* has it been closed before it's retrieved with sys::getmuxevt()? */
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_ENOENT, HAWK_NULL);
			goto done;
		}

		HAWK_ASSERT(HAWK_IN_INT_RANGE(file_node->id));
		x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 2), hawk_rtx_makeintval(rtx, file_node->id));
		if (x <= -1) goto fail;

		HAWK_ASSERT(HAWK_IN_INT_RANGE(mux_data->x_evt[index].events));
		x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 3), hawk_rtx_makeintval(rtx, mux_data->x_evt[index].events));
		if (x <= -1) goto fail;
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
#else
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, ERRNUM_TO_RC(HAWK_ENOIMPL)));
	return 0;
#endif
}

/* ------------------------------------------------------------ */

static int fnc_sockaddrdom (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_val_t* a0;
	hawk_ooch_t* addr;
	hawk_oow_t len;
	hawk_skad_t skad;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);

	a0 = hawk_rtx_getarg(rtx, 0);
	addr = hawk_rtx_getvaloocstr(rtx, a0, &len);
	if (addr)
	{
		if (hawk_gem_oocharstoskad(hawk_rtx_getgem(rtx), addr, len, &skad) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
		else
		{
			rx = hawk_skad_get_family(&skad);
		}

		hawk_rtx_freevaloocstr(rtx, a0, addr);
	}
	else
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
	}

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_socket (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	hawk_int_t rx, domain = 0, type = 0, proto = 0;
	int fd;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &domain) <= -1 || domain < 0) domain = 0;
	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &type) <= -1 || type < 0) type = 0;
	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &proto) <= -1 || proto < 0) proto = 0;

/* TOOD: SOCK_CLOEXEC, SOCK_NONBLOCK */
	fd = socket(domain, type, proto);
	if (fd >= 0)
	{
		sys_node_t* new_node;

		new_node = new_sys_node_fd(rtx, sys_list, fd);
		if (!new_node)
		{
			close(fd);
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
		else
		{
			new_node->ctx.type = SYS_NODE_DATA_TYPE_SCK; /* override the type to socket */
			rx = new_node->id;
			HAWK_ASSERT(rx >= 0);
		}
	}
	else
	{
		rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}

	HAWK_ASSERT(HAWK_IN_INT_RANGE(rx));
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_connect (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_val_t* a1;
		hawk_ooch_t* addr;
		hawk_oow_t len;
		hawk_skad_t skad;

		a1 = hawk_rtx_getarg(rtx, 1);
		addr = hawk_rtx_getvaloocstr(rtx, a1, &len);
		if (addr)
		{
			if (hawk_gem_oocharstoskad(hawk_rtx_getgem(rtx), addr, len, &skad) <= -1)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				hawk_rtx_freevaloocstr(rtx, a1, addr);
				goto done;
			}

			hawk_rtx_freevaloocstr(rtx, a1, addr);
			rx = connect(sys_node->ctx.u.file.fd, (struct sockaddr*)&skad, hawk_skad_get_size(&skad));
			if (rx <= -1)
			{
				rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
				goto done;
			}
		}
		else
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/*
 * sys:recvfrom(x, buf [, reqsize[, fromaddr]])
 */
static int fnc_recvfrom (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_int_t reqsize = 8192;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_FILE | SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_skad_t skad;
		socklen_t addrlen;

		if (hawk_rtx_getnargs(rtx) >= 3 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &reqsize) <= -1 || reqsize <= 0)) reqsize = 8192;
		if (reqsize > HAWK_INT_MAX) reqsize = HAWK_INT_MAX;

		if (reqsize > sys_list->ctx.readbuf_capa)
		{
			hawk_bch_t* tmp = hawk_rtx_reallocmem(rtx, sys_list->ctx.readbuf, reqsize);
			if (!tmp)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}
			sys_list->ctx.readbuf = tmp;
			sys_list->ctx.readbuf_capa = reqsize;
		}

		addrlen = HAWK_SIZEOF(skad);
		rx = recvfrom(sys_node->ctx.u.file.fd, sys_list->ctx.readbuf, reqsize, 0, (struct sockaddr*)&skad, &addrlen);
		if (rx <= 0)
		{
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_T("unable to read"));
			goto done;
		}
		else
		{
			hawk_val_t* sv;
			int x;

			/* avoid conflict with mixed calls to sys::read() and sys::recvfrom().
			 * sys::recvfrom() discards residue data by sys::read() and it leaves
			 * no residue by itself. */
			sys_list->ctx.readbuf_len = 0;

			sv = hawk_rtx_makembsvalwithbchars(rtx, sys_list->ctx.readbuf, rx);
			if (!sv)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}

			hawk_rtx_refupval(rtx, sv);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 1), sv);
			hawk_rtx_refdownval(rtx, sv);
			if (x <= -1)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}

			if (hawk_rtx_getnargs(rtx) >= 4)
			{
				hawk_gem_skadtooocstr (hawk_rtx_getgem(rtx), &skad, sys_list->ctx.skadbuf[0], HAWK_COUNTOF(sys_list->ctx.skadbuf[0]), HAWK_SKAD_TO_OOCSTR_ADDR | HAWK_SKAD_TO_OOCSTR_PORT);
				sv = hawk_rtx_makestrvalwithoocstr(rtx, sys_list->ctx.skadbuf[0]);
				if (!sv)
				{
					rx = copy_error_to_sys_list(rtx, sys_list);
					goto done;
				}

				hawk_rtx_refupval(rtx, sv);
				x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 3), sv);
				hawk_rtx_refdownval(rtx, sv);
				if (x <= -1)
				{
					rx = copy_error_to_sys_list(rtx, sys_list);
					goto done;
				}
			}
		}
	}

done:
	/* the value in 'rx' never exceeds HAWK_INT_MAX as 'reqsize' has been limited to
	 * it before the call to 'read'. so it's safe not to check the result of hawk_rtx_makeintval() */
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_sendto (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;
	hawk_val_t* retv;

	hawk_skad_t skad;
	struct sockaddr* sa = HAWK_NULL;
	hawk_oow_t salen = 0;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_bch_t* dptr;
		hawk_oow_t dlen;
		hawk_val_t* a1;

		if (hawk_rtx_getnargs(rtx) >= 3)
		{
			hawk_val_t* a2;
			hawk_ooch_t* addr;
			hawk_oow_t len;

			a2 = hawk_rtx_getarg(rtx, 2);
			addr = hawk_rtx_getvaloocstr(rtx, a2, &len);
			if (!addr)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}

			if (hawk_gem_oocharstoskad(hawk_rtx_getgem(rtx), addr, len, &skad) <= -1)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				hawk_rtx_freevaloocstr(rtx, a2, addr);
				goto done;
			}
			hawk_rtx_freevaloocstr(rtx, a2, addr);

			sa = (struct sockaddr*)&skad;
			salen = hawk_skad_get_size(&skad);
		}

		a1 = hawk_rtx_getarg(rtx, 1);
		dptr = hawk_rtx_getvalbcstr(rtx, a1, &dlen);
		if (dptr)
		{
			rx = sendto(sys_node->ctx.u.file.fd, dptr, dlen, 0, sa, salen);
			if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			hawk_rtx_freevalbcstr(rtx, a1, dptr);
		}
		else
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
		}
	}

done:
	retv = hawk_rtx_makeintval(rtx, rx);
	if (!retv) return -1; /* hard failure. unable to make a return value */
	hawk_rtx_setretval(rtx, retv);
	return 0;
}


static int fnc_shutdown (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_int_t how = 0;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &how) <= -1 || how < 0) how = 0;

		rx = shutdown(sys_node->ctx.u.file.fd, how);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}


static int fnc_bind (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_val_t* a1;
		hawk_ooch_t* addr;
		hawk_oow_t len;
		hawk_skad_t skad;

		a1 = hawk_rtx_getarg(rtx, 1);
		addr = hawk_rtx_getvaloocstr(rtx, a1, &len);
		if (!addr)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		if (hawk_gem_oocharstoskad(hawk_rtx_getgem(rtx), addr, len, &skad) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			hawk_rtx_freevaloocstr(rtx, a1, addr);
			goto done;
		}
		hawk_rtx_freevaloocstr(rtx, a1, addr);

		rx = bind(sys_node->ctx.u.file.fd, (struct sockaddr*)&skad, hawk_skad_get_size(&skad));
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_listen (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_int_t backlog = 0;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &backlog) <= -1 || backlog < 0) backlog = 0;

		rx = listen(sys_node->ctx.u.file.fd, backlog);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* sys::accept (s, flags, from); */
static int fnc_accept (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_skad_t skad;
		socklen_t addrlen;
		sys_node_t* new_node;
		int fd, fd_flags;
		hawk_int_t flags = 0;

		if (hawk_rtx_getnargs(rtx) >= 2 && (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &flags) <= -1 || flags < 0)) flags = 0;

		addrlen = HAWK_SIZEOF(skad);
	#if defined(HAVE_ACCEPT4)
		fd = accept4(sys_node->ctx.u.file.fd, (struct sockaddr*)&skad, &addrlen, flags);
	#else
		fd = accept(sys_node->ctx.u.file.fd, (struct sockaddr*)&skad, &addrlen);
	#endif
		if (fd <= -1)
		{
			rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
			goto done;
		}

	#if defined(HAVE_ACCEPT4)
		/* nothing to do */
	#else
		fd_flags = fcntl(fd, F_GETFD, 0);
		if (fd_flags >= 0)
		{
		#if defined(FD_CLOEXEC) && defined(SOCK_CLOEXEC)
			if (flags & SOCK_CLOEXEC) fd_flags |= FD_CLOEXEC;
		#endif
		#if defined(O_NONBLOCK) && defined(SOCK_NONBLOCK)
			if (flags & SOCK_NONBLOCK) fd_flags |= O_NONBLOCK;
		#endif
			fcntl(fd, F_SETFD, fd_flags);
		}
	#endif

		if (hawk_rtx_getnargs(rtx) >= 3)
		{
			hawk_val_t* sv;
			int x;

			hawk_gem_skadtooocstr (hawk_rtx_getgem(rtx), &skad, sys_list->ctx.skadbuf[0], HAWK_COUNTOF(sys_list->ctx.skadbuf[0]), HAWK_SKAD_TO_OOCSTR_ADDR | HAWK_SKAD_TO_OOCSTR_PORT);
			sv = hawk_rtx_makestrvalwithoocstr(rtx, sys_list->ctx.skadbuf[0]);
			if (!sv)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}

			hawk_rtx_refupval(rtx, sv);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 2), sv);
			hawk_rtx_refdownval(rtx, sv);
			if (x <= -1)
			{
				rx = copy_error_to_sys_list(rtx, sys_list);
				goto done;
			}
		}

		new_node = new_sys_node_fd(rtx, sys_list, fd);
		if (!new_node)
		{
			close(fd);
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		new_node->ctx.type = SYS_NODE_DATA_TYPE_SCK; /* override the type to socket */
		rx = new_node->id;
		HAWK_ASSERT(rx >= 0);
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}


static int fnc_setsockopt (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	hawk_int_t rx;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, hawk_rtx_getarg(rtx, 0), SYS_NODE_DATA_TYPE_SCK, &rx);
	if (sys_node)
	{
		hawk_int_t level, optname;
		int iv;
		struct timeval tv;
		void* vptr;
		hawk_oow_t vlen;

		if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &level) <= -1 ||
		    hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &optname) <= -1)
		{
		fail:
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}

		switch (optname)
		{
		/* TODO:
			case SO_BINDTODEVICE:
		*/
			case SO_BROADCAST:
			case SO_DONTROUTE:
			case SO_KEEPALIVE:
			case SO_RCVBUF:
			case SO_REUSEADDR:
		#if defined(SO_REUSEPORT)
			case SO_REUSEPORT:
		#endif
			case SO_SNDBUF:
			{
				hawk_int_t tmp;
				if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 3), &tmp) <= -1) goto fail;
				iv = tmp;
				vptr = &iv;
				vlen = HAWK_SIZEOF(iv);
				break;
			}

			case SO_RCVTIMEO:
			case SO_SNDTIMEO:
			{
				hawk_ntime_t tmp;
				if (val_to_ntime(rtx, hawk_rtx_getarg(rtx, 3), &tmp) <= -1) goto fail;
				tv.tv_sec = tmp.sec;
				tv.tv_usec = HAWK_NSEC_TO_MSEC(tmp.nsec);
				vptr = &tv;
				vlen = HAWK_SIZEOF(tv);
				break;
			}

			default:
				rx = set_error_on_sys_list(rtx, sys_list, EINVAL, HAWK_NULL);
				goto done;
		}

		rx = setsockopt(sys_node->ctx.u.file.fd, level, optname, vptr, vlen);
		if (rx <= -1) rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_NULL);
	}

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}
/* ------------------------------------------------------------ */

/*
 sys::openlog("remote://192.168.1.23:1234/test", sys::LOG_OPT_PID | sys::LOG_OPT_NDELAY, sys::LOG_FAC_LOCAL0);
 for (i = 0; i < 10; i++) sys::writelog(sys::LOG_PRI_DEBUG, "hello world " i);
 sys::closelog();

local://xxx opens the local syslog using the openlog() call in the libc library.
openlog() affects the entire process.

to open /dev/log on the local system for the current running context,
you can specify the remote:// with /dev/log or @/dev/log.
 sys::openlog("remote:///dev/log/xxx", sys::LOG_OPT_PID | sys::LOG_OPT_NDELAY, sys::LOG_FAC_LOCAL0);
 sys::openlog("remote://@/dev/log/xxx", sys::LOG_OPT_PID | sys::LOG_OPT_NDELAY, sys::LOG_FAC_LOCAL0);
 */
static void open_remote_log_socket (hawk_rtx_t* rtx, rtx_data_t* rdp)
{
	int sck, flags;
	int domain = hawk_skad_get_family(&rdp->log.skad);
	int type = SOCK_DGRAM;

	HAWK_ASSERT(rdp->log.sck <= -1);

#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
	type |= SOCK_NONBLOCK;
	type |= SOCK_CLOEXEC;
open_socket:
#endif
	sck = socket(domain, type, 0);
	if (sck <= -1)
	{
	#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
		if (errno == EINVAL && (type & (SOCK_NONBLOCK | SOCK_CLOEXEC)))
		{
			type &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC);
			goto open_socket;
		}
	#endif
		return;
	}
	else
	{
	#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
		if (type & (SOCK_NONBLOCK | SOCK_CLOEXEC)) goto done;
	#endif
	}

	flags = fcntl(sck, F_GETFD, 0);
	if (flags <= -1) return;
#if defined(FD_CLOEXEC)
	flags |= FD_CLOEXEC;
#endif
#if defined(O_NONBLOCK)
	flags |= O_NONBLOCK;
#endif
	if (fcntl(sck, F_SETFD, flags) <= -1) return;

done:
	rdp->log.sck = sck;
}

static int fnc_openlog (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_EOTHER);
	hawk_int_t opt, fac;
	hawk_ooch_t* ident = HAWK_NULL, * actual_ident;
	hawk_oow_t ident_len, actual_ident_len, pfxlen;
	hawk_bch_t* mbs_ident;
	hawk_skad_t skad;
	syslog_type_t log_type = SYSLOG_LOCAL;
	rtx_data_t* rdp = rtx_to_data(rtx, fi);
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

	ident = hawk_rtx_getvaloocstr(rtx, hawk_rtx_getarg(rtx, 0), &ident_len);
	if (!ident)
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

	/* the target name contains a null character.
	 * make system return -1 */
	if (hawk_find_oochar_in_oochars(ident, ident_len, '\0'))
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_T("invalid identifier of length %zu containing '\\0'"), ident_len);
		goto done;
	}

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 1), &opt) <= -1) goto fail;
	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 2), &fac) <= -1) goto fail;

	if (hawk_comp_oocstr_limited(ident, HAWK_T("remote://"), (pfxlen = 9), 0) == 0 ||
	    hawk_comp_oocstr_limited(ident, HAWK_T("sock://"), (pfxlen = 7), 0) == 0)
	{
		hawk_ooch_t* slash;
		/* "remote://<target-address>/syslog-identifier"
		 * "sock://<target-address>/syslog-identifier"
		 */

		log_type = SYSLOG_REMOTE;
		actual_ident = ident + pfxlen;
		actual_ident_len = ident_len - pfxlen;
		slash = hawk_rfind_oochar_in_oochars(actual_ident, actual_ident_len, '/'); // find the last slash
		if (!slash)
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_T("invalid identifier '%js' with remote address"), ident);
			goto done;
		}
		if (hawk_gem_oocharstoskad(hawk_rtx_getgem(rtx), actual_ident, slash - actual_ident, &skad) <= -1)
		{
			rx = copy_error_to_sys_list(rtx, sys_list);
			goto done;
		}
		actual_ident = slash + 1;
	}
	else if (hawk_comp_oocstr_limited(ident, HAWK_T("local://"), (pfxlen = 8), 0) == 0)
	{
		/* local://syslog-identifier */
		actual_ident = ident + pfxlen;
	}
	else
	{
		actual_ident = ident;
	}

#if defined(HAWK_OOCH_IS_BCH)
	mbs_ident = hawk_rtx_dupbcstr(rtx, actual_ident, HAWK_NULL);
#else
	mbs_ident = hawk_rtx_duputobcstr(rtx, actual_ident, HAWK_NULL);
#endif
	if (!mbs_ident)
	{
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

	if (rdp->log.ident) hawk_rtx_freemem(rtx, rdp->log.ident);
	rdp->log.ident = mbs_ident;

#if defined(ENABLE_SYSLOG)
	if (rdp->log.syslog_opened)
	{
		closelog ();
		rdp->log.syslog_opened = 0;
	}
#endif
	if (rdp->log.sck >= 0)
	{
	#if defined(_WIN32)
		/* TODO: impelement this */
	#else
		close(rdp->log.sck);
	#endif
		rdp->log.sck = -1;
	}

	rdp->log.type = log_type;
	rdp->log.opt = opt;
	rdp->log.fac = fac;
	if (rdp->log.type == SYSLOG_LOCAL)
	{
	#if defined(ENABLE_SYSLOG)
		openlog(mbs_ident, opt, fac);
		rdp->log.syslog_opened = 1;
	#endif
	}
	else if (rdp->log.type == SYSLOG_REMOTE)
	{
		rdp->log.skad = skad;
		if ((opt & LOG_NDELAY) && rdp->log.sck <= -1) open_remote_log_socket(rtx, rdp);
	}

	rx = ERRNUM_TO_RC(HAWK_ENOERR);

done:
	if (ident) hawk_rtx_freevaloocstr(rtx, hawk_rtx_getarg(rtx, 0), ident);

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_closelog (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_EOTHER);
	rtx_data_t* rdp = rtx_to_data(rtx, fi);

	switch (rdp->log.type)
	{
		case SYSLOG_LOCAL:
		#if defined(ENABLE_SYSLOG)
			closelog ();
			/* closelog() might be called without openlog(). so there is no
			 * check if syslog_opened is true.
			 * it is just used as an indicator to decide wheter closelog()
			 * should be called upon module finalization(fini). */
			rdp->log.syslog_opened = 0;
		#endif
			break;

		case SYSLOG_REMOTE:
			if (rdp->log.sck >= 0)
			{
			#if defined(_WIN32)
				/* TODO: impelement this */
			#else
				close(rdp->log.sck);
			#endif
				rdp->log.sck = -1;
			}

			if (rdp->log.dmsgbuf)
			{
				hawk_becs_close(rdp->log.dmsgbuf);
				rdp->log.dmsgbuf = HAWK_NULL;
			}

			break;
	}

	if (rdp->log.ident)
	{
		hawk_rtx_freemem(rtx, rdp->log.ident);
		rdp->log.ident = HAWK_NULL;
	}

	/* back to the local syslog in case writelog() is called
	 * without another openlog() after this closelog() */
	rdp->log.type = SYSLOG_LOCAL;

	rx = ERRNUM_TO_RC(HAWK_ENOERR);

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_writelog (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_int_t rx = ERRNUM_TO_RC(HAWK_EOTHER);
	hawk_int_t pri;
	hawk_ooch_t* msg = HAWK_NULL;
	hawk_oow_t msglen;
	rtx_data_t* rdp = rtx_to_data(rtx, fi);
	sys_list_t* sys_list = rtx_to_sys_list(rtx, fi);

	if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, 0), &pri) <= -1)
	{
	fail:
		rx = copy_error_to_sys_list(rtx, sys_list);
		goto done;
	}

	msg = hawk_rtx_getvaloocstr(rtx, hawk_rtx_getarg(rtx, 1), &msglen);
	if (!msg) goto fail;

	if (hawk_find_oochar_in_oochars(msg, msglen, '\0'))
	{
		rx = set_error_on_sys_list(rtx, sys_list, HAWK_EINVAL, HAWK_NULL);
		goto done;
	}

	if (rdp->log.type == SYSLOG_LOCAL)
	{
	#if defined(ENABLE_SYSLOG)
		#if defined(HAWK_OOCH_IS_BCH)
		syslog(pri, "%s", msg);
		#else
		{
			hawk_bch_t* mbs;
			mbs = hawk_rtx_duputobcstr(rtx, msg, HAWK_NULL);
			if (!mbs) goto fail;
			syslog(pri, "%s", mbs);
			hawk_rtx_freemem(rtx, mbs);
		}
		#endif
	#endif
	}
	else if (rdp->log.type == SYSLOG_REMOTE)
	{
	#if defined(_WIN32)
		/* TODO: implement this */
	#else

		static const hawk_bch_t* __syslog_month_names[] =
		{
			"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		};

		hawk_ntime_t now;
		struct tm tm, * tmx;
		time_t t;

		if (rdp->log.sck <= -1)
		{
			open_remote_log_socket(rtx, rdp);
			if (rdp->log.sck <= -1)
			{
				rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_T("unable to open log socket"));
				goto done;
			}
		}

		if (!rdp->log.dmsgbuf)
		{
			rdp->log.dmsgbuf = hawk_becs_open(hawk_rtx_getgem(rtx), 0, 0);
			if (!rdp->log.dmsgbuf) goto fail;
		}

		if (hawk_get_ntime(&now) <= -1)
		{
			rx = set_error_on_sys_list(rtx, sys_list, HAWK_ESYSERR, HAWK_T("unable to get time"));
			goto done;
		}

		t = now.sec;
	#if defined(HAVE_LOCALTIME_R)
		tmx = localtime_r(&t, &tm);
	#else
		tmx = localtime(&t);
	#endif
		if (!tmx)
		{
			rx = set_error_on_sys_list_with_errno(rtx, sys_list, HAWK_T("unable to get local time"));
			goto done;
		}

		if (hawk_becs_fmt(
			rdp->log.dmsgbuf, HAWK_BT("<%d>%hs %02d %02d:%02d:%02d "),
			(int)(rdp->log.fac | pri),
			__syslog_month_names[tmx->tm_mon], tmx->tm_mday,
			tmx->tm_hour, tmx->tm_min, tmx->tm_sec) == (hawk_oow_t)-1) goto fail;

		if (rdp->log.ident || (rdp->log.opt & LOG_PID))
		{
			/* if the identifier is set or LOG_PID is set, the produced tag won't be empty.
				* so appending ':' is kind of ok */
			if (hawk_becs_fcat(rdp->log.dmsgbuf, HAWK_BT("%hs"), (rdp->log.ident? rdp->log.ident: HAWK_BT(""))) == (hawk_oow_t)-1) goto fail;
			if ((rdp->log.opt & LOG_PID) && hawk_becs_fcat(rdp->log.dmsgbuf, HAWK_BT("[%d]"), (int)HAWK_GETPID()) == (hawk_oow_t)-1) goto fail;
			if (hawk_becs_fcat(rdp->log.dmsgbuf, HAWK_BT(": ")) == (hawk_oow_t)-1) goto fail;
		}

	#if defined(HAWK_OOCH_IS_BCH)
		if (hawk_becs_fcat(rdp->log.dmsgbuf, HAWK_BT("%hs"), msg) == (hawk_oow_t)-1) goto fail;
	#else
		if (hawk_becs_fcat(rdp->log.dmsgbuf, HAWK_BT("%ls"), msg) == (hawk_oow_t)-1) goto fail;
	#endif

		/* don't care about output failure */
		if (sendto(rdp->log.sck, HAWK_BECS_PTR(rdp->log.dmsgbuf), HAWK_BECS_LEN(rdp->log.dmsgbuf),
		           0, (struct sockaddr*)&rdp->log.skad, hawk_skad_get_size(&rdp->log.skad)) <= -1)
		{
			hawk_ooch_t msg[256] = HAWK_T("unable to write to log socket ");
			hawk_gem_skadtooocstr(hawk_rtx_getgem(rtx), &rdp->log.skad, &msg[30], HAWK_COUNTOF(msg) - 30, HAWK_SKAD_TO_OOCSTR_ADDR | HAWK_SKAD_TO_OOCSTR_PORT);
			rx = set_error_on_sys_list_with_errno(rtx, sys_list, msg);
			goto done;
		}
	#endif
	}

	rx = ERRNUM_TO_RC(HAWK_ENOERR);

done:
	if (msg) hawk_rtx_freevaloocstr(rtx, hawk_rtx_getarg(rtx, 1), msg);

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* ------------------------------------------------------------ */

#define ENDIAN_BIG    1
#define ENDIAN_LITTLE 2
#if defined(HAWK_ENDIAN_BIG)
#	define ENDIAN_NATIVE ENDIAN_BIG
#else
#	define ENDIAN_NATIVE ENDIAN_LITTLE
#endif

#if 0
struct st_uint16_t { hawk_uint8_t x; hawk_uint16_t y; };
struct st_uint32_t { hawk_uint8_t x; hawk_uint32_t y; };
struct st_uint64_t { hawk_uint8_t x; hawk_uint64_t y; };
struct st_intptr_t { hawk_uint8_t x; hawk_uintptr_t y; };
struct st_float_t { hawk_uint8_t x; float y; };
struct st_double_t { hawk_uint8_t x; double y; };
struct st_pointer_t { hawk_uint8_t x; void* y; };

#define AL_UINT16 (HAWK_SIZEOF(struct st_uint16_t) - HAWK_SIZEOF(hawk_uint16_t))
#define AL_UINT32 (HAWK_SIZEOF(struct st_uint32_t) - HAWK_SIZEOF(hawk_uint32_t))
#define AL_UINT64 (HAWK_SIZEOF(struct st_uint64_t) - HAWK_SIZEOF(hawk_uint64_t))
#define AL_UINTPTR (HAWK_SIZEOF(struct st_uintptr_t) - HAWK_SIZEOF(hawk_uintptr_t))
#define AL_FLOAT (HAWK_SIZEOF(struct st_float_t) - HAWK_SIZEOF(float))
#define AL_DOUBLE (HAWK_SIZEOF(struct st_double_t) - HAWK_SIZEOF(double))
#define AL_POINTER (HAWK_SIZEOF(struct st_pointer_t) - HAWK_SIZEOF(void*))
#endif

/*
Byte-Order
=	native
<	little-endian
>	big-endian
!	network (= big-endian)

b	hawk_int8_t
B	hawk_uint8_t
h	hawk_int16_t
H	hawk_uint16_t
i	hawk_int32_t
I	hawk_uint32_t
l	hawk_int64_t
L	hawk_uint64_t
q	hawk_intmax_t
Q	hawk_uintmax_t
n	hawk_intptr_t
N	hawk_uintptr_t
f	hawk_flt_t (32 bits)
d	hawk_flt_t (64 bits)
s	char[]
p	char[]
x	pad bytes
*/

static hawk_oow_t pack_uint16_t (hawk_uint8_t* dst, hawk_uint16_t val, int endian)
{
	if (endian == ENDIAN_LITTLE)
	{
		*dst++ = val;
		*dst++ = val >> 8;
	}
	else
	{
		*dst++ = val >> 8;
		*dst++ = val;
	}

	return 2;
}

static hawk_oow_t pack_uint32_t (hawk_uint8_t* dst, hawk_uint32_t val, int endian)
{
	if (endian == ENDIAN_LITTLE)
	{
		*dst++ = val;
		*dst++ = val >> 8;
		*dst++ = val >> 16;
		*dst++ = val >> 24;
	}
	else
	{
		*dst++ = val >> 24;
		*dst++ = val >> 16;
		*dst++ = val >> 8;
		*dst++ = val;
	}

	return 4;
}

static hawk_oow_t pack_uint64_t (hawk_uint8_t* dst, hawk_uint64_t val, int endian)
{
	if (endian == ENDIAN_LITTLE)
	{
		*dst++ = val;
		*dst++ = val >> 8;
		*dst++ = val >> 16;
		*dst++ = val >> 24;
		*dst++ = val >> 32;
		*dst++ = val >> 40;
		*dst++ = val >> 48;
		*dst++ = val >> 56;
	}
	else
	{
		*dst++ = val >> 56;
		*dst++ = val >> 48;
		*dst++ = val >> 40;
		*dst++ = val >> 32;
		*dst++ = val >> 24;
		*dst++ = val >> 16;
		*dst++ = val >> 8;
		*dst++ = val;
	}

	return 8;
}

static hawk_oow_t pack_uintmax_t (hawk_uint8_t* dst, hawk_uintmax_t val, int endian)
{
	hawk_uintmax_t i;

	if (endian == ENDIAN_LITTLE)
	{
		for (i = 0; i < HAWK_SIZEOF(hawk_uintmax_t); i++) *dst++ = val >> (i * 8);
	}
	else
	{
		for (i = HAWK_SIZEOF(hawk_uintmax_t); i > 0; ) *dst++ = val >> ((--i) * 8);
	}
	return HAWK_SIZEOF(hawk_uintmax_t);
}

static hawk_oow_t pack_uintptr_t (hawk_uint8_t* dst, hawk_uintptr_t val, int endian)
{
	hawk_uintptr_t i;

	if (endian == ENDIAN_LITTLE)
	{
		for (i = 0; i < HAWK_SIZEOF(hawk_uintptr_t); i++) *dst++ = val >> (i * 8);
	}
	else
	{
		for (i = HAWK_SIZEOF(hawk_uintptr_t); i > 0; ) *dst++ = val >> ((--i) * 8);
	}
	return HAWK_SIZEOF(hawk_uintptr_t);
}

static int ensure_pack_buf (hawk_rtx_t* rtx, rtx_data_t* rdp, hawk_oow_t reqsz)
{
	if (reqsz > rdp->pack.capa - rdp->pack.len)
	{
		hawk_uint8_t* tmp;
		hawk_oow_t newcapa;

		newcapa = HAWK_ALIGN_POW2(rdp->pack.capa + reqsz, 256);
		if (rdp->pack.ptr == rdp->pack.__static_buf)
		{
			tmp = hawk_rtx_allocmem(rtx, newcapa);
			if (HAWK_UNLIKELY(!tmp)) return -1;
			HAWK_MEMCPY (tmp, rdp->pack.__static_buf, rdp->pack.len);
		}
		else
		{
			tmp = hawk_rtx_reallocmem(rtx, rdp->pack.ptr, newcapa);
			if (HAWK_UNLIKELY(!tmp)) return -1;
		}

		rdp->pack.ptr = tmp;
		rdp->pack.capa = newcapa;
	}

	return 0;
}

static hawk_int_t pack_data (hawk_rtx_t* rtx, const hawk_oocs_t* fmt, const hawk_fnc_info_t* fi, rtx_data_t* rdp)
{
	hawk_oow_t rep_cnt, rep_set, rc;
	const hawk_ooch_t* fmtp, *fmte;
	hawk_oow_t arg_idx, arg_cnt;
	int endian = ENDIAN_NATIVE;

#define PACK_CHECK_ARG_AND_BUF(reqarg, reqsz) do { \
	if (arg_cnt - arg_idx < reqarg) return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EARGTF, HAWK_NULL); \
	if (ensure_pack_buf(rtx, rdp, reqsz)  <= -1) goto oops_internal; \
} while(0)

	rdp->pack.len = 0;

	arg_idx = 2; /* set past the format specifier */
	arg_cnt = hawk_rtx_getnargs(rtx);

	rep_cnt = 1;
	rep_set = 0;

	fmte = fmt->ptr + fmt->len;
	for (fmtp = fmt->ptr; fmtp < fmte; fmtp++)
	{
		switch (*fmtp)
		{
		#if 0
			case '@': /* native size, native alignment */
				break;
		#endif

			case '=': /* native endian, no alignment */
				endian = ENDIAN_NATIVE;
				break;

			case '<': /* little-endian, no alignment */
				endian = ENDIAN_LITTLE;
				break;

			case '>': /* big-endian, no alignment */
			case '!': /* network, no alignment */
				endian = ENDIAN_BIG;
				break;

			case 'x': /* zero-padding */
				PACK_CHECK_ARG_AND_BUF(0, rep_cnt * HAWK_SIZEOF(hawk_uint8_t));
				for (rc = 0; rc < rep_cnt; rc++) rdp->pack.ptr[rdp->pack.len++] = 0;
				break;

			case 'b': /* byte, char */
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_int8_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.ptr[rdp->pack.len++] = (hawk_int8_t)v;
				}
				break;
			}

			case 'B':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint8_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.ptr[rdp->pack.len++] = (hawk_uint8_t)v;
				}
				break;
			}

			case 'h': /* 2 bytes signed */
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_int16_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uint16_t(&rdp->pack.ptr[rdp->pack.len], (hawk_int16_t)v, endian);
				}
				break;
			}

			case 'H': /* 2 bytes unsigned */
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint16_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uint16_t(&rdp->pack.ptr[rdp->pack.len], (hawk_uint16_t)v, endian);
				}
				break;
			}

			case 'i':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_int32_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uint32_t(&rdp->pack.ptr[rdp->pack.len], (hawk_int32_t)v, endian);
				}
				break;
			}

			case 'I':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint32_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uint32_t(&rdp->pack.ptr[rdp->pack.len], (hawk_uint32_t)v, endian);
				}
				break;
			}

			case 'l':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_int64_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uint64_t(&rdp->pack.ptr[rdp->pack.len], (hawk_int64_t)v, endian);
				}
				break;
			}

			case 'L':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint64_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uint64_t(&rdp->pack.ptr[rdp->pack.len], (hawk_uint64_t)v, endian);
				}
				break;
			}

			case 'q':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_intmax_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uintmax_t(&rdp->pack.ptr[rdp->pack.len], (hawk_intmax_t)v, endian);
				}
				break;
			}

			case 'Q':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uintmax_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uintmax_t(&rdp->pack.ptr[rdp->pack.len], (hawk_uintmax_t)v, endian);
				}
				break;
			}

			case 'n':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_intptr_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uintptr_t(&rdp->pack.ptr[rdp->pack.len], (hawk_intptr_t)v, endian);
				}
				break;
			}

			case 'N':
			{
				hawk_int_t v;
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uintptr_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoint(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					rdp->pack.len += pack_uintptr_t(&rdp->pack.ptr[rdp->pack.len], (hawk_uintptr_t)v, endian);
				}
				break;
			}

			case 'f':
			{

				hawk_flt_t v;
				float x;
				hawk_uint32_t y;
				HAWK_ASSERT(HAWK_SIZEOF(float) == HAWK_SIZEOF(hawk_uint32_t));
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint32_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoflt(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					x = (float)v;
					HAWK_MEMCPY (&y, &x, HAWK_SIZEOF(y));
					rdp->pack.len += pack_uint32_t(&rdp->pack.ptr[rdp->pack.len], y, endian);
				}
				break;
			}

			case 'd':
			{
				hawk_flt_t v;
				double x;
				hawk_uint64_t y;
				HAWK_ASSERT(HAWK_SIZEOF(double) == HAWK_SIZEOF(hawk_uint64_t));
				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint64_t) * rep_cnt);
				for (rc = 0; rc < rep_cnt; rc++)
				{
					if (hawk_rtx_valtoflt(rtx, hawk_rtx_getarg(rtx, arg_idx++), &v) <= -1) goto oops_internal;
					x = (double)v;
					HAWK_MEMCPY (&y, &x, HAWK_SIZEOF(y));
					rdp->pack.len += pack_uint64_t(&rdp->pack.ptr[rdp->pack.len], y, endian);
				}
				break;
			}

			case 'c':
			{
				hawk_val_t* a;
				hawk_bcs_t tmp;

				PACK_CHECK_ARG_AND_BUF(rep_cnt, HAWK_SIZEOF(hawk_uint8_t) * rep_cnt);

				for (rc = 0; rc < rep_cnt; rc++)
				{
					a = hawk_rtx_getarg(rtx, arg_idx++);

					tmp.ptr = hawk_rtx_getvalbcstr(rtx, a, &tmp.len);
					if (HAWK_UNLIKELY(!tmp.ptr)) goto oops_internal;

					if (tmp.len < 1)
					{
						hawk_rtx_freevalbcstr(rtx, a, tmp.ptr);
						return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EINVAL, HAWK_T("data too short for '%jc'"), *fmtp);
					}
					rdp->pack.ptr[rdp->pack.len++] = tmp.ptr[0];
					hawk_rtx_freevalbcstr(rtx, a, tmp.ptr);
				}
				break;
			}


			case 's':
			case 'p':
			{
				hawk_val_t* a;
				hawk_bcs_t tmp;

				PACK_CHECK_ARG_AND_BUF(1, HAWK_SIZEOF(hawk_uint8_t) * rep_cnt);

				a = hawk_rtx_getarg(rtx, arg_idx++);

				tmp.ptr = hawk_rtx_getvalbcstr(rtx, a, &tmp.len);
				if (HAWK_UNLIKELY(!tmp.ptr)) goto oops_internal;

				if (rep_cnt > tmp.len)
				{
					hawk_rtx_freevalbcstr(rtx, a, tmp.ptr);
					return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EINVAL, HAWK_T("data too short for '%jc'"), *fmtp);
				}

				for (rc = 0; rc < rep_cnt; rc++)
				{
					rdp->pack.ptr[rdp->pack.len++] = (hawk_uint8_t)tmp.ptr[rc];
				}
				hawk_rtx_freevalbcstr(rtx, a, tmp.ptr);
				break;
			}

			default:
				if (hawk_is_ooch_digit(*fmtp))
				{
					if (!rep_set)
					{
						rep_cnt = 0;
						rep_set = 1;
					}
					rep_cnt = rep_cnt * 10 + (*fmtp - '0');
				}
				else if (!hawk_is_ooch_space(*fmtp))
				{
					return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EINVAL, HAWK_T("invalid specifier - %jc"), *fmtp);
				}
				break;
		}

		if (!hawk_is_ooch_digit(*fmtp) && !hawk_is_ooch_space(*fmtp))
		{
			rep_cnt = 1;
			rep_set = 0;
		}
	}

	return 0;

oops_internal:
	return copy_error_to_sys_list(rtx, &rdp->sys_list);
}

static hawk_uint16_t unpack_uint16 (const hawk_uint8_t* binp, int endian)
{
	hawk_uint16_t v;

	if (endian == ENDIAN_LITTLE)
	{
		v = *binp++;
		v |= (hawk_uint16_t)(*binp++) << 8;
	}
	else
	{
		v = (hawk_uint16_t)(*binp++) << 8;
		v |= *binp++;
	}
	return v;
}

static hawk_int16_t unpack_int16 (const hawk_uint8_t* binp, int endian)
{
	hawk_uint16_t v = unpack_uint16(binp, endian);
	return (v <= HAWK_TYPE_MAX(hawk_int16_t))? (hawk_int16_t)v: ((hawk_int16_t)-1 - (hawk_int16_t)(HAWK_TYPE_MAX(hawk_uint16_t) - v));
}

static hawk_uint32_t unpack_uint32 (const hawk_uint8_t* binp, int endian)
{
	hawk_uint32_t v;

	if (endian == ENDIAN_LITTLE)
	{
		v = *binp++;
		v |= (hawk_uint32_t)(*binp++) << 8;
		v |= (hawk_uint32_t)(*binp++) << 16;
		v |= (hawk_uint32_t)(*binp++) << 24;
	}
	else
	{
		v = (hawk_uint32_t)(*binp++) << 24;
		v |= (hawk_uint32_t)(*binp++) << 16;
		v |= (hawk_uint32_t)(*binp++) << 8;
		v |= *binp++;
	}
	return v;
}

static hawk_int32_t unpack_int32 (const hawk_uint8_t* binp, int endian)
{
	hawk_uint32_t v = unpack_uint32 (binp, endian);
	return (v <= HAWK_TYPE_MAX(hawk_int32_t))? (hawk_int32_t)v: ((hawk_int32_t)-1 - (hawk_int32_t)(HAWK_TYPE_MAX(hawk_uint32_t) - v));
}

static hawk_uint64_t unpack_uint64 (const hawk_uint8_t* binp, int endian)
{
	hawk_uint64_t v;

	if (endian == ENDIAN_LITTLE)
	{
		v = *binp++;
		v |= (hawk_uint64_t)(*binp++) << 8;
		v |= (hawk_uint64_t)(*binp++) << 16;
		v |= (hawk_uint64_t)(*binp++) << 24;
		v |= (hawk_uint64_t)(*binp++) << 32;
		v |= (hawk_uint64_t)(*binp++) << 40;
		v |= (hawk_uint64_t)(*binp++) << 48;
		v |= (hawk_uint64_t)(*binp++) << 56;
	}
	else
	{
		v = (hawk_uint64_t)(*binp++) << 56;
		v |= (hawk_uint64_t)(*binp++) << 48;
		v |= (hawk_uint64_t)(*binp++) << 40;
		v |= (hawk_uint64_t)(*binp++) << 32;
		v |= (hawk_uint64_t)(*binp++) << 24;
		v |= (hawk_uint64_t)(*binp++) << 16;
		v |= (hawk_uint64_t)(*binp++) << 8;
		v |= *binp++;
	}
	return v;
}

static hawk_int64_t unpack_int64 (const hawk_uint8_t* binp, int endian)
{
	hawk_uint64_t v = unpack_uint64 (binp, endian);
	return (v <= HAWK_TYPE_MAX(hawk_int64_t))? (hawk_int64_t)v: ((hawk_int64_t)-1 - (hawk_int64_t)(HAWK_TYPE_MAX(hawk_uint64_t) - v));
}

static hawk_uintmax_t unpack_uintmax (const hawk_uint8_t* binp, int endian)
{
	hawk_uintmax_t v = 0;
	hawk_oow_t i;

	if (endian == ENDIAN_LITTLE)
	{
		for (i = 0; i < HAWK_SIZEOF(hawk_uintmax_t); i++) v |= (hawk_uintmax_t)(*binp++) << (i * 8);
	}
	else
	{
		for (i = HAWK_SIZEOF(hawk_uintmax_t); i > 0; ) v |= (hawk_uintmax_t)(*binp++) << ((--i) * 8);
	}
	return v;
}

static hawk_intmax_t unpack_intmax (const hawk_uint8_t* binp, int endian)
{
	hawk_uintmax_t v = unpack_uintmax(binp, endian);
	return (v <= HAWK_TYPE_MAX(hawk_intmax_t))? (hawk_intmax_t)v: ((hawk_intmax_t)-1 - (hawk_intmax_t)(HAWK_TYPE_MAX(hawk_uintmax_t) - v));
}

static hawk_uintptr_t unpack_uintptr (const hawk_uint8_t* binp, int endian)
{
	hawk_uintptr_t v = 0;
	hawk_oow_t i;

	if (endian == ENDIAN_LITTLE)
	{
		for (i = 0; i < HAWK_SIZEOF(hawk_uintptr_t); i++) v |= (hawk_uintptr_t)(*binp++) << (i * 8);
	}
	else
	{
		for (i = HAWK_SIZEOF(hawk_uintptr_t); i > 0; ) v |= (hawk_uintptr_t)(*binp++) << ((--i) * 8);
	}
	return v;
}

static hawk_intptr_t unpack_intptr (const hawk_uint8_t* binp, int endian)
{
	hawk_uintptr_t v = unpack_uintptr(binp, endian);
	return (v <= HAWK_TYPE_MAX(hawk_intptr_t))? (hawk_intptr_t)v: ((hawk_intptr_t)-1 - (hawk_intptr_t)(HAWK_TYPE_MAX(hawk_uintptr_t) - v));
}

static hawk_int_t unpack_data (hawk_rtx_t* rtx, const hawk_bcs_t* bin, const hawk_oocs_t* fmt, const hawk_fnc_info_t* fi, rtx_data_t* rdp)
{
	const hawk_ooch_t* fmtp, * fmte;
	const hawk_uint8_t* binp, * bine;
	hawk_oow_t rep_cnt, rep_set, rc;
	hawk_oow_t arg_idx, arg_cnt;
	int endian = ENDIAN_NATIVE;
	hawk_val_t* v;

#define UNPACK_CHECK_ARG_AND_DATA(reqarg, reqsz) do { \
	if (arg_cnt - arg_idx < reqarg) return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EARGTF, HAWK_NULL); \
	if (bine - binp < reqsz) return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EINVAL, HAWK_T("insufficient binary data")); \
} while(0)

	arg_idx = 2; /* set past the format specifier */
	arg_cnt = hawk_rtx_getnargs(rtx);

	rep_cnt = 1;
	rep_set = 0;

	binp = (hawk_uint8_t*)bin->ptr;
	bine = (hawk_uint8_t*)bin->ptr + bin->len;
	fmte = fmt->ptr + fmt->len;
	for (fmtp = fmt->ptr; fmtp < fmte; fmtp++)
	{
		switch (*fmtp)
		{
			case '=': /* native */
				endian = ENDIAN_NATIVE;
				break;

			case '<': /* little-endian */
				endian = ENDIAN_LITTLE;
				break;

			case '>': /* big-endian */
			case '!': /* network */
				endian = ENDIAN_BIG;
				break;

			case 'x':
				binp += rep_cnt;
				break;

			case 'b':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_int8_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, (hawk_int8_t)*binp++);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'B':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_int8_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, *binp++);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'h':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_int16_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_int16(binp, endian));
					binp += HAWK_SIZEOF(hawk_int16_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'H':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uint16_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_uint16(binp, endian));
					binp += HAWK_SIZEOF(hawk_uint16_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'i':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_int32_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_int32(binp, endian));
					binp += HAWK_SIZEOF(hawk_int32_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'I':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uint32_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_uint32(binp, endian));
					binp += HAWK_SIZEOF(hawk_uint32_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'l':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_int64_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_int64(binp, endian));
					binp += HAWK_SIZEOF(hawk_int64_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'L':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uint64_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_uint64(binp, endian));
					binp += HAWK_SIZEOF(hawk_uint64_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'q':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_intmax_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_intmax(binp, endian));
					binp += HAWK_SIZEOF(hawk_intmax_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'Q':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uintmax_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_uintmax(binp, endian));
					binp += HAWK_SIZEOF(hawk_uintmax_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'n':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_intptr_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_intptr(binp, endian));
					binp += HAWK_SIZEOF(hawk_intptr_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'N':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uintptr_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makeintval(rtx, unpack_uintptr(binp, endian));
					binp += HAWK_SIZEOF(hawk_uintptr_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'f':
			{
				hawk_uint32_t x;
				float y;
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uint32_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					x = unpack_uint32(binp, endian);
					HAWK_MEMCPY (&y, &x, HAWK_SIZEOF(y));
					v = hawk_rtx_makefltval(rtx, y);
					binp += HAWK_SIZEOF(hawk_uint32_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'd':
			{
				hawk_uint64_t x;
				double y;
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uint64_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					x = unpack_uint64(binp, endian);
					HAWK_MEMCPY (&y, &x, HAWK_SIZEOF(y));
					v = hawk_rtx_makefltval(rtx, y);
					binp += HAWK_SIZEOF(hawk_uint64_t);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 'c':
			{
				UNPACK_CHECK_ARG_AND_DATA(rep_cnt, rep_cnt * HAWK_SIZEOF(hawk_uint8_t));
				for (rc = 0; rc < rep_cnt; rc++)
				{
					v = hawk_rtx_makebchrval(rtx, *binp++);
					if (HAWK_UNLIKELY(!v)) goto oops_internal;
					if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				}
				break;
			}

			case 's':
			case 'p':
			{
				UNPACK_CHECK_ARG_AND_DATA(1, rep_cnt);
				v = hawk_rtx_makembsvalwithbchars(rtx, (const hawk_bch_t*)binp, rep_cnt);
				binp += rep_cnt;
				if (HAWK_UNLIKELY(!v)) goto oops_internal;
				if (hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, arg_idx++), v) <= -1) goto oops_internal;
				break;
			}

			default:
				if (hawk_is_ooch_digit(*fmtp))
				{
					if (!rep_set)
					{
						rep_cnt = 0;
						rep_set = 1;
					}
					rep_cnt = rep_cnt * 10 + (*fmtp - '0');
				}
				else if (!hawk_is_ooch_space(*fmtp))
				{
					return set_error_on_sys_list(rtx, &rdp->sys_list, HAWK_EINVAL, HAWK_T("invalid specifier - %jc"), *fmtp);
				}
				break;
		}

		if (!hawk_is_ooch_digit(*fmtp) && !hawk_is_ooch_space(*fmtp))
		{
			rep_cnt = 1;
			rep_set = 0;
		}
	}

	return 0;

oops_internal:
	return copy_error_to_sys_list(rtx, &rdp->sys_list);
}

/*
 sys::pack(bin, "i 5s h", 10, "hello", -20);
 printf ("%W\n", bin);
*/
static int fnc_pack (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	rtx_data_t* rdp = rtx_to_data(rtx, fi);
	hawk_val_t* a0;
	hawk_oocs_t fmt;
	hawk_int_t rx = 0;

	a0 = hawk_rtx_getarg(rtx, 1);
	fmt.ptr = hawk_rtx_getvaloocstr(rtx, a0, &fmt.len);
	if (HAWK_UNLIKELY(!fmt.ptr))
	{
	fail:
		rx = copy_error_to_sys_list(rtx, &rdp->sys_list);
	}
	else
	{
		rx = pack_data(rtx, &fmt, fi, rdp);
		hawk_rtx_freevaloocstr(rtx, a0, fmt.ptr);

		if (rx >= 0)
		{
			hawk_val_t* tmp;
			int x;

			tmp = hawk_rtx_makembsvalwithbchars(rtx, (const hawk_bch_t*)rdp->pack.ptr, rdp->pack.len);
			if (HAWK_UNLIKELY(!tmp)) goto fail;

			hawk_rtx_refupval(rtx, tmp);
			x = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 0), tmp);
			hawk_rtx_refdownval(rtx, tmp);
			if (x <= -1) goto fail;
		}
	}

	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;
}

/* sys::unpack(@b"\x00\x11\x12\x13\x14\x15", "h h h", a, b, c);
 * print a, b, c;
 */
static int fnc_unpack (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	rtx_data_t* rdp = rtx_to_data(rtx, fi);
	hawk_val_t* a0, * a1;
	hawk_oocs_t fmt;
	hawk_bcs_t bin;
	hawk_int_t rx = 0;

	fmt.ptr = HAWK_NULL;
	bin.ptr = HAWK_NULL;

	a0 = hawk_rtx_getarg(rtx, 0);
	a1 = hawk_rtx_getarg(rtx, 1);

	bin.ptr = hawk_rtx_getvalbcstr(rtx, a0, &bin.len);
	if (HAWK_UNLIKELY(!bin.ptr)) goto fail;

	fmt.ptr = hawk_rtx_getvaloocstr(rtx, a1, &fmt.len);
	if (HAWK_UNLIKELY(!fmt.ptr)) goto fail;

	rx = unpack_data(rtx, &bin, &fmt, fi, rdp);

	hawk_rtx_freevaloocstr(rtx, a1, fmt.ptr); fmt.ptr = HAWK_NULL;
	hawk_rtx_freevalbcstr(rtx, a0, bin.ptr); bin.ptr = HAWK_NULL;

done:
	hawk_rtx_setretval(rtx, hawk_rtx_makeintval(rtx, rx));
	return 0;

fail:
	rx = copy_error_to_sys_list(rtx, &rdp->sys_list);
	if (bin.ptr) hawk_rtx_freevalbcstr(rtx, a0, bin.ptr);
	if (fmt.ptr) hawk_rtx_freevaloocstr(rtx, a1, fmt.ptr);
	goto done;
}

/* ----------------------------------------------------------------------- */
static int fnc_basename (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_val_t* a0, * tmp;
	hawk_oocs_t path;
	const hawk_ooch_t* ptr;

	a0 = hawk_rtx_getarg(rtx, 0);

	path.ptr = hawk_rtx_getvaloocstr(rtx, a0, &path.len);
	if (HAWK_UNLIKELY(!path.ptr)) return -1;

	ptr = hawk_get_base_name_oochars(path.ptr, path.len);

	tmp = hawk_rtx_makestrvalwithoocstr(rtx, ptr);
	hawk_rtx_freevaloocstr(rtx, a0, path.ptr);
	if (HAWK_UNLIKELY(!tmp)) return -1;

	hawk_rtx_setretval(rtx, tmp);
	return 0;
}

static int fnc_dirname (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_val_t* a0, * tmp;
	hawk_oocs_t path;
	const hawk_ooch_t* ptr;
	hawk_oow_t len;

	a0 = hawk_rtx_getarg(rtx, 0);

	path.ptr = hawk_rtx_getvaloocstr(rtx, a0, &path.len);
	if (HAWK_UNLIKELY(!path.ptr)) return -1;

	ptr = hawk_get_base_name_oochars(path.ptr, path.len);

	len = ptr - path.ptr;
	if (len > 1) len--;
	tmp = hawk_rtx_makestrvalwithoochars(rtx, path.ptr, len);
	hawk_rtx_freevaloocstr(rtx, a0, path.ptr);
	if (HAWK_UNLIKELY(!tmp)) return -1;

	hawk_rtx_setretval(rtx, tmp);
	return 0;
}

/* ----------------------------------------------------------------------- */

#define A_MAX HAWK_TYPE_MAX(hawk_oow_t)

static hawk_mod_fnc_tab_t fnctab[] =
{
	/* keep this table sorted for binary search in query(). */

#if defined(WCOREDUMP)
	{ HAWK_T("WCOREDUMP"),   { { 1, 1, HAWK_NULL       }, fnc_wcoredump,   0  } },
#endif
	{ HAWK_T("WEXITSTATUS"), { { 1, 1, HAWK_NULL       }, fnc_wexitstatus, 0  } },
	{ HAWK_T("WIFEXITED"),   { { 1, 1, HAWK_NULL       }, fnc_wifexited,   0  } },
	{ HAWK_T("WIFSIGNALED"), { { 1, 1, HAWK_NULL       }, fnc_wifsignaled, 0  } },
	{ HAWK_T("WTERMSIG"),    { { 1, 1, HAWK_NULL       }, fnc_wtermsig,    0  } },
	{ HAWK_T("accept"),      { { 1, 3, HAWK_T("vvr")   }, fnc_accept,      0  } },
	{ HAWK_T("addtomux"),    { { 3, 3, HAWK_NULL       }, fnc_addtomux,    0  } },
	{ HAWK_T("basename"),    { { 1, 1, HAWK_NULL       }, fnc_basename,    0  } },
	{ HAWK_T("bind"),        { { 2, 2, HAWK_NULL       }, fnc_bind,        0  } },
	{ HAWK_T("chmod"),       { { 2, 2, HAWK_NULL       }, fnc_chmod,       0  } },
	{ HAWK_T("chroot"),      { { 1, 1, HAWK_NULL       }, fnc_chroot,      0  } },
	{ HAWK_T("close"),       { { 1, 2, HAWK_NULL       }, fnc_close,       0  } },
	{ HAWK_T("closedir"),    { { 1, 1, HAWK_NULL       }, fnc_closedir,    0  } },
	{ HAWK_T("closelog"),    { { 0, 0, HAWK_NULL       }, fnc_closelog,    0  } },
	{ HAWK_T("closemux"),    { { 1, 1, HAWK_NULL       }, fnc_closemux,    0  } },
	{ HAWK_T("connect"),     { { 2, 2, HAWK_NULL       }, fnc_connect,     0  } },
	{ HAWK_T("delfrommux"),  { { 2, 2, HAWK_NULL       }, fnc_delfrommux,  0  } },
	{ HAWK_T("dirname"),     { { 1, 1, HAWK_NULL       }, fnc_dirname,     0  } },
	{ HAWK_T("dup"),         { { 1, 3, HAWK_NULL       }, fnc_dup,         0  } },
	{ HAWK_T("errmsg"),      { { 0, 0, HAWK_NULL       }, fnc_errmsg,      0  } },
	{ HAWK_T("fchmod"),      { { 2, 2, HAWK_NULL       }, fnc_fchmod,      0  } },
	{ HAWK_T("fchown"),      { { 3, 3, HAWK_NULL       }, fnc_fchown,      0  } },
	{ HAWK_T("fcntl"),       { { 2, 3, HAWK_NULL       }, fnc_fcntl,       0  } },
	{ HAWK_T("flock"),       { { 5, 6, HAWK_T("vvvvvr")}, fnc_flock,       0  } },
	{ HAWK_T("fork"),        { { 0, 0, HAWK_NULL       }, fnc_fork,        0  } },
	{ HAWK_T("fseek"),       { { 3, 3, HAWK_NULL       }, fnc_fseek,       0  } },
	{ HAWK_T("getegid"),     { { 0, 0, HAWK_NULL       }, fnc_getegid,     0  } },
	{ HAWK_T("getenv"),      { { 2, 2, HAWK_T("vr")    }, fnc_getenv,      0  } },
	{ HAWK_T("geteuid"),     { { 0, 0, HAWK_NULL       }, fnc_geteuid,     0  } },
	{ HAWK_T("getgid"),      { { 0, 0, HAWK_NULL       }, fnc_getgid,      0  } },
	{ HAWK_T("getifcfg"),    { { 3, 3, HAWK_T("vvr")   }, fnc_getifcfg,    0  } },
	{ HAWK_T("getmuxevt"),   { { 4, 4, HAWK_T("vvrr")  }, fnc_getmuxevt,   0  } },
	{ HAWK_T("getnwifcfg"),  { { 3, 3, HAWK_T("vvr")   }, fnc_getifcfg,    0  } }, /* backward compatibility */
	{ HAWK_T("getpgid"),     { { 0, 0, HAWK_NULL       }, fnc_getpgid,     0  } },
	{ HAWK_T("getpid"),      { { 0, 0, HAWK_NULL       }, fnc_getpid,      0  } },
	{ HAWK_T("getppid"),     { { 0, 0, HAWK_NULL       }, fnc_getppid,     0  } },
	{ HAWK_T("gettid"),      { { 0, 0, HAWK_NULL       }, fnc_gettid,      0  } },
	{ HAWK_T("gettime"),     { { 0, 1, HAWK_T("r")     }, fnc_gettime,     0  } },
	{ HAWK_T("getuid"),      { { 0, 0, HAWK_NULL       }, fnc_getuid,      0  } },
	{ HAWK_T("kill"),        { { 2, 2, HAWK_NULL       }, fnc_kill,        0  } },
	{ HAWK_T("listen"),      { { 2, 2, HAWK_NULL       }, fnc_listen,      0  } },
	{ HAWK_T("mkdir"),       { { 1, 2, HAWK_NULL       }, fnc_mkdir,       0  } },
	{ HAWK_T("mktime"),      { { 0, 1, HAWK_NULL       }, fnc_mktime,      0  } },
	{ HAWK_T("modinmux"),    { { 3, 3, HAWK_NULL       }, fnc_modinmux,    0  } },
	{ HAWK_T("open"),        { { 2, 3, HAWK_NULL       }, fnc_open,        0  } },
	{ HAWK_T("opendir"),     { { 1, 2, HAWK_NULL       }, fnc_opendir,     0  } },
	{ HAWK_T("openfd"),      { { 1, 1, HAWK_NULL       }, fnc_openfd,      0  } },
	{ HAWK_T("openlog"),     { { 3, 3, HAWK_NULL       }, fnc_openlog,     0  } },
	{ HAWK_T("openmux"),     { { 0, 0, HAWK_NULL       }, fnc_openmux,     0  } },
	{ HAWK_T("pack"),        { { 2, A_MAX, HAWK_T("rv")}, fnc_pack,        0 } },
	{ HAWK_T("pipe"),        { { 2, 3, HAWK_T("rrv")   }, fnc_pipe,        0  } },
	{ HAWK_T("read"),        { { 2, 4, HAWK_T("vrvv")  }, fnc_read,        0  } },
	{ HAWK_T("readdir"),     { { 2, 2, HAWK_T("vr")    }, fnc_readdir,     0  } },
	{ HAWK_T("recvfrom"),    { { 2, 4, HAWK_T("vrvr")  }, fnc_recvfrom,    0  } },
	{ HAWK_T("resetdir"),    { { 2, 2, HAWK_NULL       }, fnc_resetdir,    0  } },
	{ HAWK_T("rmdir"),       { { 1, 1, HAWK_NULL       }, fnc_rmdir,       0  } },
	{ HAWK_T("sendto"),      { { 2, 3, HAWK_NULL       }, fnc_sendto,      0  } },
	{ HAWK_T("setenv"),      { { 2, 3, HAWK_NULL       }, fnc_setenv,      0  } },
	{ HAWK_T("setsockopt"),  { { 4, 4, HAWK_NULL       }, fnc_setsockopt,  0  } },
	{ HAWK_T("settime"),     { { 1, 1, HAWK_NULL       }, fnc_settime,     0  } },
	{ HAWK_T("shutdown"),    { { 2, 2, HAWK_NULL       }, fnc_shutdown,    0  } },
	{ HAWK_T("sleep"),       { { 1, 1, HAWK_NULL       }, fnc_sleep,       0  } },
	{ HAWK_T("sockaddrdom"), { { 1, 1, HAWK_NULL       }, fnc_sockaddrdom, 0  } },
	{ HAWK_T("socket"),      { { 3, 3, HAWK_NULL       }, fnc_socket,      0  } },
	{ HAWK_T("stat"),        { { 2, 2, HAWK_T("vr")    }, fnc_stat,        0  } },
	{ HAWK_T("strftime"),    { { 2, 3, HAWK_NULL       }, fnc_strftime,    0  } },
	{ HAWK_T("symlink"),     { { 2, 2, HAWK_NULL       }, fnc_symlink,     0  } },
	{ HAWK_T("system"),      { { 1, 1, HAWK_NULL       }, fnc_system,      0  } },
	{ HAWK_T("systime"),     { { 0, 1, HAWK_T("r")     }, fnc_gettime,     0  } }, /* alias to gettime() */
	{ HAWK_T("tcflush"),     { { 2, 2, HAWK_NULL       }, fnc_tcflush,     0  } },
	{ HAWK_T("tcgetattr"),   { { 2, 2, HAWK_T("vr")    }, fnc_tcgetattr,   0  } },
	{ HAWK_T("tcsetattr"),   { { 3, 3, HAWK_NULL       }, fnc_tcsetattr,   0  } },
	{ HAWK_T("tcsetraw"),    { { 1, 1, HAWK_NULL       }, fnc_tcsetraw,    0  } },
	{ HAWK_T("unlink"),      { { 1, 1, HAWK_NULL       }, fnc_unlink,      0  } },
	{ HAWK_T("unpack"),      { { 2, A_MAX, HAWK_T("vvr")  }, fnc_unpack,   0  } },
	{ HAWK_T("unsetenv"),    { { 1, 1, HAWK_NULL       }, fnc_unsetenv,    0  } },
	{ HAWK_T("wait"),        { { 1, 3, HAWK_T("vrv")   }, fnc_wait,        0  } },
	{ HAWK_T("waitonmux"),   { { 2, 2, HAWK_T("vv")    }, fnc_waitonmux,   0  } },
	{ HAWK_T("write"),       { { 2, 4, HAWK_NULL       }, fnc_write,       0  } },
	{ HAWK_T("writelog"),    { { 2, 2, HAWK_NULL       }, fnc_writelog,    0  } }
};

#if !defined(SIGHUP)
#	define SIGHUP 1
#endif
#if !defined(SIGINT)
#	define SIGINT 2
#endif
#if !defined(SIGQUIT)
#	define SIGQUIT 3
#endif
#if !defined(SIGABRT)
#	define SIGABRT 6
#endif
#if !defined(SIGKILL)
#	define SIGKILL 9
#endif
#if !defined(SIGSEGV)
#	define SIGSEGV 11
#endif
#if !defined(SIGALRM)
#	define SIGALRM 14
#endif
#if !defined(SIGTERM)
#	define SIGTERM 15
#endif

static hawk_mod_int_tab_t inttab[] =
{
	/* keep this table sorted for binary search in query(). */

	{ HAWK_T("AF_INET"),            { AF_INET } },
	{ HAWK_T("AF_INET6"),           { AF_INET6 } },

	{ HAWK_T("C_KEEPFD"),           { CLOSE_KEEPFD } },

	{ HAWK_T("DIR_SORT"),           { HAWK_DIR_SORT } },

	{ HAWK_T("FD_CLOEXEC"),         { FD_CLOEXEC } },

	{ HAWK_T("FLOCK_GET"),          { FLOCK_GET } }, /* bitwise-OR with another FLOCK_READ/WRITE/WAIT value */
	{ HAWK_T("FLOCK_READ"),         { F_RDLCK } },
	{ HAWK_T("FLOCK_UNLOCK"),       { F_UNLCK } },
	{ HAWK_T("FLOCK_WAIT"),         { FLOCK_WAIT } }, /* bitwise-OR with another FLOCK_READ/WRITE/WAIT value */
	{ HAWK_T("FLOCK_WRITE"),        { F_WRLCK } },

	{ HAWK_T("F_GETFD"),            { F_GETFD } },
	{ HAWK_T("F_GETFL"),            { F_GETFL } },
	{ HAWK_T("F_SETFD"),            { F_SETFD } },
	{ HAWK_T("F_SETFL"),            { F_SETFL } },

	{ HAWK_T("IFCFG_IN4"),          { HAWK_IFCFG_IN4 } },
	{ HAWK_T("IFCFG_IN6"),          { HAWK_IFCFG_IN6 } },

#if defined(ENABLE_SYSLOG)
	{ HAWK_T("LOG_FAC_AUTH"),       { LOG_AUTH } },
	{ HAWK_T("LOG_FAC_AUTHPRIV"),   { LOG_AUTHPRIV } },
	{ HAWK_T("LOG_FAC_CRON"),       { LOG_CRON } },
	{ HAWK_T("LOG_FAC_DAEMON"),     { LOG_DAEMON } },
	{ HAWK_T("LOG_FAC_FTP"),        { LOG_FTP } },
	{ HAWK_T("LOG_FAC_KERN"),       { LOG_KERN } },
	{ HAWK_T("LOG_FAC_LOCAL0"),     { LOG_LOCAL0 } },
	{ HAWK_T("LOG_FAC_LOCAL1"),     { LOG_LOCAL1 } },
	{ HAWK_T("LOG_FAC_LOCAL2"),     { LOG_LOCAL2 } },
	{ HAWK_T("LOG_FAC_LOCAL3"),     { LOG_LOCAL3 } },
	{ HAWK_T("LOG_FAC_LOCAL4"),     { LOG_LOCAL4 } },
	{ HAWK_T("LOG_FAC_LOCAL5"),     { LOG_LOCAL5 } },
	{ HAWK_T("LOG_FAC_LOCAL6"),     { LOG_LOCAL6 } },
	{ HAWK_T("LOG_FAC_LOCAL7"),     { LOG_LOCAL7 } },
	{ HAWK_T("LOG_FAC_LPR"),        { LOG_LPR } },
	{ HAWK_T("LOG_FAC_MAIL"),       { LOG_MAIL } },
	{ HAWK_T("LOG_FAC_NEWS"),       { LOG_NEWS } },
	{ HAWK_T("LOG_FAC_SYSLOG"),     { LOG_SYSLOG } },
	{ HAWK_T("LOG_FAC_USER"),       { LOG_USER } },
	{ HAWK_T("LOG_FAC_UUCP"),       { LOG_UUCP } },

	{ HAWK_T("LOG_OPT_CONS"),       { LOG_CONS } },
	{ HAWK_T("LOG_OPT_NDELAY"),     { LOG_NDELAY } },
	{ HAWK_T("LOG_OPT_NOWAIT"),     { LOG_NOWAIT } },
	{ HAWK_T("LOG_OPT_PID"),        { LOG_PID } },

	{ HAWK_T("LOG_PRI_ALERT"),      { LOG_ALERT } },
	{ HAWK_T("LOG_PRI_CRIT"),       { LOG_CRIT } },
	{ HAWK_T("LOG_PRI_DEBUG"),      { LOG_DEBUG } },
	{ HAWK_T("LOG_PRI_EMERG"),      { LOG_EMERG } },
	{ HAWK_T("LOG_PRI_ERR"),        { LOG_ERR } },
	{ HAWK_T("LOG_PRI_INFO"),       { LOG_INFO } },
	{ HAWK_T("LOG_PRI_NOTICE"),     { LOG_NOTICE } },
	{ HAWK_T("LOG_PRI_WARNING"),    { LOG_WARNING } },
#endif

	{ HAWK_T("MUX_EVT_ERR"),  { MUX_EVT_ERR } },
	{ HAWK_T("MUX_EVT_HUP"),  { MUX_EVT_HUP } },
	{ HAWK_T("MUX_EVT_IN"),   { MUX_EVT_IN } },
	{ HAWK_T("MUX_EVT_OUT"),  { MUX_EVT_OUT } },

	{ HAWK_T("NWIFCFG_IN4"),  { HAWK_IFCFG_IN4 } }, /* for backward compatibility */
	{ HAWK_T("NWIFCFG_IN6"),  { HAWK_IFCFG_IN6 } }, /* for backward compatibility */

#if defined(O_APPEND)
	{ HAWK_T("O_APPEND"),     { O_APPEND } },
#endif
#if defined(O_ASYNC)
	{ HAWK_T("O_ASYNC"),      { O_ASYNC } },
#endif
#if defined(O_CLOEXEC)
	{ HAWK_T("O_CLOEXEC"),    { O_CLOEXEC } },
#endif
#if defined(O_CREAT)
	{ HAWK_T("O_CREAT"),      { O_CREAT } },
#endif
#if defined(O_DIRECTORY)
	{ HAWK_T("O_DIRECTORY"),  { O_DIRECTORY } },
#endif
#if defined(O_DSYNC)
	{ HAWK_T("O_DSYNC"),      { O_DSYNC } },
#endif
#if defined(O_EXCL)
	{ HAWK_T("O_EXCL"),       { O_EXCL } },
#endif
#if defined(O_NOATIME)
	{ HAWK_T("O_NOATIME"),    { O_NOATIME} },
#endif
#if defined(O_NOCTTY)
	{ HAWK_T("O_NOCTTY"),     { O_NOCTTY} },
#endif
#if defined(O_NOFOLLOW)
	{ HAWK_T("O_NOFOLLOW"),   { O_NOFOLLOW } },
#endif
#if defined(O_NONBLOCK)
	{ HAWK_T("O_NONBLOCK"),   { O_NONBLOCK } },
#endif
#if defined(O_RDONLY)
	{ HAWK_T("O_RDONLY"),     { O_RDONLY } },
#endif
#if defined(O_RDWR)
	{ HAWK_T("O_RDWR"),       { O_RDWR } },
#endif
#if defined(O_SYNC)
	{ HAWK_T("O_SYNC"),       { O_SYNC } },
#endif
#if defined(O_TRUNC)
	{ HAWK_T("O_TRUNC"),      { O_TRUNC } },
#endif
#if defined(O_WRONLY)
	{ HAWK_T("O_WRONLY"),     { O_WRONLY } },
#endif

	{ HAWK_T("RC_EACCES"),       { -HAWK_EACCES } },
	{ HAWK_T("RC_EAGAIN"),       { -HAWK_EAGAIN } },
	{ HAWK_T("RC_EBUFFULL"),     { -HAWK_EBUFFULL} },
	{ HAWK_T("RC_EBUSY"),        { -HAWK_EBUSY} },
	{ HAWK_T("RC_ECHILD"),       { -HAWK_ECHILD } },
	{ HAWK_T("RC_EECERR"),       { -HAWK_EECERR } },
	{ HAWK_T("RC_EEXIST"),       { -HAWK_EEXIST } },
	{ HAWK_T("RC_EINPROG"),      { -HAWK_EINPROG } },
	{ HAWK_T("RC_EINTERN"),      { -HAWK_EINTERN } },
	{ HAWK_T("RC_EINTR"),        { -HAWK_EINTR } },
	{ HAWK_T("RC_EINVAL"),       { -HAWK_EINVAL } },
	{ HAWK_T("RC_EIOERR"),       { -HAWK_EIOERR } },
	{ HAWK_T("RC_EISDIR"),       { -HAWK_EISDIR } },
	{ HAWK_T("RC_ENOENT"),       { -HAWK_ENOENT } },
	{ HAWK_T("RC_ENOHND"),       { -HAWK_ENOHND } },
	{ HAWK_T("RC_ENOIMPL"),      { -HAWK_ENOIMPL } },
	{ HAWK_T("RC_ENOMEM"),       { -HAWK_ENOMEM } },
	{ HAWK_T("RC_ENOSUP"),       { -HAWK_ENOSUP } },
	{ HAWK_T("RC_ENOTDIR"),      { -HAWK_ENOTDIR } },
	{ HAWK_T("RC_EOTHER"),       { -HAWK_EOTHER } },
	{ HAWK_T("RC_EPERM"),        { -HAWK_EPERM } },
	{ HAWK_T("RC_EPIPE"),        { -HAWK_EPIPE } },
	{ HAWK_T("RC_ESTATE"),       { -HAWK_ESTATE } },
	{ HAWK_T("RC_ESYSERR"),      { -HAWK_ESYSERR } },
	{ HAWK_T("RC_ETMOUT"),       { -HAWK_ETMOUT } },

	{ HAWK_T("SEEK_CUR"),        { SEEK_CUR } },
	{ HAWK_T("SEEK_END"),        { SEEK_END } },
	{ HAWK_T("SEEK_SET"),        { SEEK_SET } },

	{ HAWK_T("SHUT_RD"),         { SHUT_RD } },
	{ HAWK_T("SHUT_RDWR"),       { SHUT_RDWR } },
	{ HAWK_T("SHUT_WR"),         { SHUT_WR } },

	{ HAWK_T("SIGABRT"),         { SIGABRT } },
	{ HAWK_T("SIGALRM"),         { SIGALRM } },
	{ HAWK_T("SIGHUP"),          { SIGHUP } },
	{ HAWK_T("SIGINT"),          { SIGINT } },
	{ HAWK_T("SIGKILL"),         { SIGKILL } },
	{ HAWK_T("SIGQUIT"),         { SIGQUIT } },
	{ HAWK_T("SIGSEGV"),         { SIGSEGV } },
	{ HAWK_T("SIGTERM"),         { SIGTERM } },

	{ HAWK_T("SIZEOF_FLT"),      { HAWK_SIZEOF_FLT_T } },
	{ HAWK_T("SIZEOF_FLTBAS"),   { HAWK_SIZEOF_FLTBAS_T } },
	{ HAWK_T("SIZEOF_FLTMAX"),   { HAWK_SIZEOF_FLTMAX_T } },
	{ HAWK_T("SIZEOF_INT"),      { HAWK_SIZEOF_INT_T } },
	{ HAWK_T("SIZEOF_INTMAX"),   { HAWK_SIZEOF_INTMAX_T } },
	{ HAWK_T("SIZEOF_INTPTR"),   { HAWK_SIZEOF_INTPTR_T } },


	{ HAWK_T("SOCK_CLOEXEC"),    { X_SOCK_CLOEXEC } },
	{ HAWK_T("SOCK_DGRAM"),      { SOCK_DGRAM } },
	{ HAWK_T("SOCK_NONBLOCK"),   { X_SOCK_NONBLOCK } },
	{ HAWK_T("SOCK_STREAM"),     { SOCK_STREAM } },

	{ HAWK_T("SOL_SOCKET"),      { SOL_SOCKET } },

	{ HAWK_T("SO_BROADCAST"),    { SO_BROADCAST } },
	{ HAWK_T("SO_DONTROUTE"),    { SO_DONTROUTE } },
	{ HAWK_T("SO_KEEPALIVE"),    { SO_KEEPALIVE } },
	{ HAWK_T("SO_RCVBUF"),       { SO_RCVBUF } },
	{ HAWK_T("SO_RCVTIMEO"),     { SO_RCVTIMEO } },
	{ HAWK_T("SO_REUSEADDR"),    { SO_REUSEADDR } },
	{ HAWK_T("SO_REUSEPORT"),    { X_SO_REUSEPORT } },
	{ HAWK_T("SO_SNDBUF"),       { SO_SNDBUF } },
	{ HAWK_T("SO_SNDTIMEO"),     { SO_SNDTIMEO } },

	{ HAWK_T("STRFTIME_UTC"),    { STRFTIME_UTC } },

#if defined(VDISCARD)
	{ HAWK_T("TC_CC_VDISCARD"),  { VDISCARD } },
#endif
	{ HAWK_T("TC_CC_VEOF"),      { VEOF } },
	{ HAWK_T("TC_CC_VEOL"),      { VEOL } },
	{ HAWK_T("TC_CC_VEOL2"),     { VEOL2 } },
	{ HAWK_T("TC_CC_VERASE"),    { VERASE } },
	{ HAWK_T("TC_CC_VINTR"),     { VINTR } },
	{ HAWK_T("TC_CC_VKILL"),     { VKILL } },
#if defined(VLNEXT)
	{ HAWK_T("TC_CC_VLNEXT"),    { VLNEXT } },
#endif
	{ HAWK_T("TC_CC_VMIN"),      { VMIN } },
	{ HAWK_T("TC_CC_VQUIT"),     { VQUIT } },
#if defined(VREPRINT)
	{ HAWK_T("TC_CC_VREPRINT"),  { VREPRINT } },
#endif
	{ HAWK_T("TC_CC_VSTART"),    { VSTART } },
	{ HAWK_T("TC_CC_VSTOP"),     { VSTOP } },
	{ HAWK_T("TC_CC_VSUSP"),     { VSUSP } },
#if defined(VSWTC)
	{ HAWK_T("TC_CC_VSWTC"),     { VSWTC } },  /* hard to define with an alternative value when it's not available */
#endif
	{ HAWK_T("TC_CC_VTIME"),     { VTIME } },
#if defined(VWERASE)
	{ HAWK_T("TC_CC_VWERASE"),   { VWERASE } },
#endif

	{ HAWK_T("TC_CFLAG_B0"),      { B0 } },
	{ HAWK_T("TC_CFLAG_B110"),    { B110 } },
	{ HAWK_T("TC_CFLAG_B115200"), { B115200 } },
	{ HAWK_T("TC_CFLAG_B1200"),   { B1200 } },
	{ HAWK_T("TC_CFLAG_B134"),    { B134 } },
	{ HAWK_T("TC_CFLAG_B150"),    { B150 } },
	{ HAWK_T("TC_CFLAG_B1800"),   { B1800 } },
	{ HAWK_T("TC_CFLAG_B19200"),  { B19200 } },
	{ HAWK_T("TC_CFLAG_B200"),    { B200 } },
	{ HAWK_T("TC_CFLAG_B230400"), { B230400 } },
	{ HAWK_T("TC_CFLAG_B2400"),   { B2400 } },
	{ HAWK_T("TC_CFLAG_B300"),    { B300 } },
	{ HAWK_T("TC_CFLAG_B38400"),  { B38400 } },
#if defined(B460800)
	{ HAWK_T("TC_CFLAG_B460800"), { B460800 } },
#endif
	{ HAWK_T("TC_CFLAG_B4800"),   { B4800 } },
	{ HAWK_T("TC_CFLAG_B50"),     { B50 } },
	{ HAWK_T("TC_CFLAG_B57600"),  { B57600 } },
	{ HAWK_T("TC_CFLAG_B600"),    { B600 } },
	{ HAWK_T("TC_CFLAG_B75"),     { B75 } },
#if defined(B921600)
	{ HAWK_T("TC_CFLAG_B921600"), { B921600 } },
#endif
	{ HAWK_T("TC_CFLAG_B9600"),   { B9600 } },

	{ HAWK_T("TC_CFLAG_CLOCAL"),  { CLOCAL } },
	{ HAWK_T("TC_CFLAG_CREAD"),   { CREAD } },
	{ HAWK_T("TC_CFLAG_CRTSCTS"), { CRTSCTS } },
	{ HAWK_T("TC_CFLAG_CS5"),     { CS5 } },
	{ HAWK_T("TC_CFLAG_CS6"),     { CS6 } },
	{ HAWK_T("TC_CFLAG_CS7"),     { CS7 } },
	{ HAWK_T("TC_CFLAG_CS8"),     { CS8 } },
	{ HAWK_T("TC_CFLAG_CSIZE"),   { CSIZE } },
	{ HAWK_T("TC_CFLAG_CSTOPB"),  { CSTOPB } },
	{ HAWK_T("TC_CFLAG_HUPCL"),   { HUPCL } },
	{ HAWK_T("TC_CFLAG_PARENB"),  { PARENB } },
	{ HAWK_T("TC_CFLAG_PARODD"),  { PARODD } },

	{ HAWK_T("TC_IFLAG_BRKINT"), { BRKINT } },
	{ HAWK_T("TC_IFLAG_ICRNL"),  { ICRNL } },
	{ HAWK_T("TC_IFLAG_IGNBRK"), { IGNBRK } },
	{ HAWK_T("TC_IFLAG_IGNCR"),  { IGNCR } },
	{ HAWK_T("TC_IFLAG_IGNPAR"), { IGNPAR } },
#if defined(IMAXBEL)
	{ HAWK_T("TC_IFLAG_IMAXBEL"),{ IMAXBEL } },
#endif
	{ HAWK_T("TC_IFLAG_INLCR"),  { INLCR } },
	{ HAWK_T("TC_IFLAG_INPCK"),  { INPCK } },
	{ HAWK_T("TC_IFLAG_ISTRIP"), { ISTRIP } },
	{ HAWK_T("TC_IFLAG_IUCLC"),  { X_IUCLC } },
	{ HAWK_T("TC_IFLAG_IUTF8"),  { X_IUTF8 } },
	{ HAWK_T("TC_IFLAG_IXANY"),  { IXANY } },
	{ HAWK_T("TC_IFLAG_IXOFF"),  { IXOFF } },
	{ HAWK_T("TC_IFLAG_IXON"),   { IXON } },
	{ HAWK_T("TC_IFLAG_PARMRK"), { PARMRK } },

	{ HAWK_T("TC_IFLUSH"),       { TCIFLUSH } },
	{ HAWK_T("TC_IOFLUSH"),      { TCIOFLUSH } },
	{ HAWK_T("TC_OFLUSH"),       { TCOFLUSH } },

	{ HAWK_T("TC_LFLAG_ECHO"),   { ECHO  } },
	{ HAWK_T("TC_LFLAG_ECHOE"),  { ECHOE  } },
	{ HAWK_T("TC_LFLAG_ECHOK"),  { ECHOK  } },
	{ HAWK_T("TC_LFLAG_ECHONL"), { ECHONL  } },
	{ HAWK_T("TC_LFLAG_ICANON"), { ICANON  } },
	{ HAWK_T("TC_LFLAG_ISIG"),   { ISIG  } },
	{ HAWK_T("TC_LFLAG_NOFLSH"), { NOFLSH  } },
	{ HAWK_T("TC_LFLAG_TOSTOP"), { TOSTOP  } },

	{ HAWK_T("TC_OFLAG_OCRNL"),  { OCRNL  } },
	{ HAWK_T("TC_OFLAG_ONLCR"),  { ONLCR  } },
	{ HAWK_T("TC_OFLAG_ONLRET"), { ONLRET  } },
	{ HAWK_T("TC_OFLAG_ONOCR"),  { ONOCR  } },
	{ HAWK_T("TC_OFLAG_ONOEOT"), { X_ONOEOT  } },
	{ HAWK_T("TC_OFLAG_OPOST"),  { OPOST  } },
	{ HAWK_T("TC_OFLAG_OXTABS"), { X_OXTABS  } },

	{ HAWK_T("TC_SADRAIN"),      { TCSADRAIN  } },
	{ HAWK_T("TC_SAFLUSH"),      { TCSAFLUSH } },
	{ HAWK_T("TC_SANOW"),        { TCSANOW  } },

	{ HAWK_T("WNOHANG"),         { WNOHANG } }
};

static int query (hawk_mod_t* mod, hawk_t* hawk, const hawk_ooch_t* name, hawk_mod_sym_t* sym)
{
	if (hawk_findmodsymfnc_noseterr(hawk, fnctab, HAWK_COUNTOF(fnctab), name, sym) >= 0) return 0;
	return hawk_findmodsymint(hawk, inttab, HAWK_COUNTOF(inttab), name, sym);
}

/* TODO: proper resource management */

static int init (hawk_mod_t* mod, hawk_rtx_t* rtx)
{
	mod_ctx_t* mctx = (mod_ctx_t*)mod->ctx;
	rtx_data_t rd, * rdp;
	hawk_rbt_pair_t* pair;

	HAWK_MEMSET (&rd, 0, HAWK_SIZEOF(rd));
	pair = hawk_rbt_insert(mctx->rtxtab, &rtx, HAWK_SIZEOF(rtx), &rd, HAWK_SIZEOF(rd));
	if (HAWK_UNLIKELY(!pair)) return -1;

	rdp = (rtx_data_t*)HAWK_RBT_VPTR(pair);
	__init_sys_list(rtx, &rdp->sys_list);

	rdp->pack.ptr = rdp->pack.__static_buf;
	rdp->pack.capa = HAWK_COUNTOF(rdp->pack.__static_buf);
	rdp->pack.len = 0;

	rdp->log.type = SYSLOG_LOCAL;
	rdp->log.syslog_opened = 0;
	rdp->log.sck = -1;

	return 0;
}

static void __fini_log (hawk_rtx_t* rtx, rtx_data_t* rdp)
{
#if defined(ENABLE_SYSLOG)
	if (rdp->log.syslog_opened)
	{
		/* closelog() only if openlog() has been called explicitly.
		 * if you call writelog() functions without openlog() and
		 * end yoru program without closelog(), the program may leak
		 * some resources created by the writelog() function. (e.g.
		 * socket to /dev/log) */
		closelog ();
		rdp->log.syslog_opened = 0;
	}
#endif

	if (rdp->log.sck >= 0)
	{
	#if defined(_WIN32)
		/* TODO: implement this */
	#else
		close(rdp->log.sck);
	#endif
		rdp->log.sck = -1;
	}

	if (rdp->log.dmsgbuf)
	{
		hawk_becs_close(rdp->log.dmsgbuf);
		rdp->log.dmsgbuf = HAWK_NULL;
	}

	if (rdp->log.ident)
	{
		hawk_rtx_freemem(rtx, rdp->log.ident);
		rdp->log.ident = HAWK_NULL;
	}
}

static void fini (hawk_mod_t* mod, hawk_rtx_t* rtx)
{
	/* TODO:
	for (each pid for rtx) kill (pid, SIGKILL);
	for (each pid for rtx) waitpid (pid, HAWK_NULL, 0);
	*/

	mod_ctx_t* mctx = (mod_ctx_t*)mod->ctx;
	hawk_rbt_pair_t* pair;

	/* garbage clean-up */
	pair = hawk_rbt_search(mctx->rtxtab, &rtx, HAWK_SIZEOF(rtx));
	if (pair)
	{
		rtx_data_t* rdp;

		rdp = (rtx_data_t*)HAWK_RBT_VPTR(pair);

		__fini_log(rtx, rdp);

		if (rdp->pack.ptr != rdp->pack.__static_buf) hawk_rtx_freemem(rtx, rdp->pack.ptr);

		if (rdp->sys_list.ctx.readbuf)
		{
			hawk_rtx_freemem(rtx, rdp->sys_list.ctx.readbuf);
			rdp->sys_list.ctx.readbuf = HAWK_NULL;
			rdp->sys_list.ctx.readbuf_capa = 0;
		}

		__fini_sys_list(rtx, &rdp->sys_list);

		hawk_rbt_delete(mctx->rtxtab, &rtx, HAWK_SIZEOF(rtx));
	}
}

static void unload (hawk_mod_t* mod, hawk_t* hawk)
{
	mod_ctx_t* mctx = (mod_ctx_t*)mod->ctx;

	HAWK_ASSERT(HAWK_RBT_SIZE(mctx->rtxtab) == 0);
	hawk_rbt_close(mctx->rtxtab);

	hawk_freemem(hawk, mctx);
}

int hawk_mod_sys (hawk_mod_t* mod, hawk_t* hawk)
{
	hawk_rbt_t* rbt;

	mod->query = query;
	mod->unload = unload;

	mod->init = init;
	mod->fini = fini;

	mod->ctx = hawk_callocmem(hawk, HAWK_SIZEOF(mod_ctx_t));
	if (HAWK_UNLIKELY(!mod->ctx)) return -1;

	rbt = hawk_rbt_open(hawk_getgem(hawk), 0, 1, 1);
	if (HAWK_UNLIKELY(!rbt))
	{
		hawk_freemem(hawk, mod->ctx);
		return -1;
	}
	hawk_rbt_setstyle(rbt, hawk_get_rbt_style(HAWK_RBT_STYLE_INLINE_COPIERS));

	((mod_ctx_t*)mod->ctx)->rtxtab = rbt;
	return 0;
}
