/*
 * $Id$
 *
    Copyright (c) 2006-2019 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 "val.h" /* for QSE_AWK_QUICKINT_MAX */
#include <qse/cmn/str.h>
#include <qse/cmn/chr.h>
#include <qse/cmn/time.h>
#include <qse/cmn/mbwc.h>
#include <qse/cmn/rbt.h>
#include <qse/si/nwad.h>
#include <qse/si/nwif.h>
#include <qse/si/sio.h>
#include <qse/si/dir.h>
#include "../cmn/mem-prv.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
#	include "../cmn/syscall.h"
#	if defined(HAVE_SYS_SYSCALL_H)
#		include <sys/syscall.h>
#	endif

#	define ENABLE_SYSLOG
#	include <syslog.h>

#	include <sys/socket.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)

enum sys_rc_t
{
	RC_ERROR = -1,
	RC_ENOIMPL = -2,
	RC_ENOSYS = -3,
	RC_ENOMEM = -4,
	RC_EAGAIN = -5,
	RC_EINTR = -6,
	RC_EINVAL = -7,
	RC_ECHILD = -8,
	RC_EPERM = -9,
	RC_EBADF = -10,
	RC_ENOENT = -11,
	RC_EEXIST = -12,
	RC_ENOTDIR = -13
};
typedef enum sys_rc_t sys_rc_t;

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

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

struct mod_ctx_t
{
	qse_rbt_t* rtxtab;

	struct
	{
		syslog_type_t type;
		char* ident;
		qse_skad_t skad;
		int syslog_opened; // has openlog() been called?
		int opt;
		int fac;
		int sck;
		qse_mbs_t* dmsgbuf;
	} log;
};
typedef struct mod_ctx_t mod_ctx_t;

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

enum sys_node_data_type_t
{
	SYS_NODE_DATA_FD,
	SYS_NODE_DATA_DIR
};
typedef enum sys_node_data_type_t sys_node_data_type_t;

struct sys_node_data_t
{
	sys_node_data_type_t type;
	union
	{
		int fd;
		qse_dir_t* dir;
	} u;
};
typedef struct sys_node_data_t sys_node_data_t;

struct sys_list_data_t
{
	qse_char_t errmsg[256];
	qse_mchar_t* readbuf;
	qse_size_t readbuf_capa;
};
typedef struct sys_list_data_t sys_list_data_t;

#define __IMAP_NODE_T_DATA sys_node_data_t ctx;
#define __IMAP_LIST_T_DATA sys_list_data_t ctx;
#define __IMAP_LIST_T sys_list_t
#define __IMAP_NODE_T sys_node_t
#define __MAKE_IMAP_NODE __new_sys_node
#define __FREE_IMAP_NODE __free_sys_node
#include "imap-imp.h"

struct rtx_data_t
{
	sys_list_t sys_list;
};
typedef struct rtx_data_t rtx_data_t;

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

static QSE_INLINE sys_rc_t syserr_to_rc (int syserr)
{
	switch (syserr)
	{
	#if defined(EAGAIN) && defined(EWOULDBLOCK) && (EAGAIN == EWOULDBLOCK)
		case EAGAIN: return RC_EAGAIN;
	#elif defined(EAGAIN) && defined(EWOULDBLOCK) && (EAGAIN != EWOULDBLOCK)
		case EAGAIN: case EWOULDBLOCK: return RC_EAGAIN;
	#elif defined(EAGAIN)
		case EAGAIN: return RC_EAGAIN;
	#elif defined(EWOULDBLOCK)
		case EWOULDBLOCK: return RC_EAGAIN;
	#endif

		case EBADF: return RC_EBADF;
		case ECHILD: return RC_ECHILD;
		case EINTR:  return RC_EINTR;
		case EINVAL: return RC_EINVAL;
		case ENOMEM:  return RC_ENOMEM;
		case ENOSYS:  return RC_ENOSYS;
		case EPERM:  return RC_EPERM;

		default: return RC_ERROR;
	}
}

static QSE_INLINE sys_rc_t direrr_to_rc (qse_dir_errnum_t direrr)
{
	switch (direrr)
	{
		case QSE_DIR_ENOIMPL: return RC_ENOIMPL;
		case QSE_DIR_ENOMEM: return RC_ENOMEM;
		case QSE_DIR_EINVAL: return RC_EINVAL;
		case QSE_DIR_EPERM: return RC_EPERM;
		case QSE_DIR_ENOENT: return RC_ENOENT;
		case QSE_DIR_EEXIST: return RC_EEXIST;
		case QSE_DIR_ENOTDIR: return RC_ENOTDIR;
		case QSE_DIR_EINTR: return RC_EINTR;
		case QSE_DIR_EAGAIN: return RC_EAGAIN;

		default: return RC_ERROR;
	}
}

static QSE_INLINE sys_rc_t awkerr_to_rc (qse_dir_errnum_t awkerr)
{
	switch (awkerr)
	{
		case QSE_AWK_ENOIMPL: return RC_ENOIMPL;
		case QSE_AWK_ENOMEM: return RC_ENOMEM;
		case QSE_AWK_EINVAL: return RC_EINVAL;
		case QSE_AWK_EPERM: return RC_EPERM;
		case QSE_AWK_ENOENT: return RC_ENOENT;
		case QSE_AWK_EEXIST: return RC_EEXIST;

		default: return RC_ERROR;
	}
}


static const qse_char_t* rc_to_errstr (sys_rc_t rc)
{
	switch (rc)
	{
		case RC_EAGAIN:  return QSE_T("resource temporarily unavailable");
		case RC_EBADF:   return QSE_T("bad file descriptor");
		case RC_ECHILD:  return QSE_T("no child processes");
		case RC_EEXIST:  return QSE_T("file exists");
		case RC_EINTR:   return QSE_T("interrupted");
		case RC_EINVAL:  return QSE_T("invalid argument");
		case RC_ENOENT:  return QSE_T("no such file or directory");
		case RC_ENOIMPL: return QSE_T("not implemented"); /* not implemented in this module */
		case RC_ENOMEM:  return QSE_T("not enough space");
		case RC_ENOTDIR: return QSE_T("not a directory");
		case RC_ENOSYS:  return QSE_T("not implemented in system");
		case RC_EPERM:   return QSE_T("operation not permitted");
		case RC_ERROR:   return QSE_T("error");
		default:         return QSE_T("unknown error");
	};
}

static void set_errmsg_on_sys_list (qse_awk_rtx_t* rtx, sys_list_t* sys_list, const qse_char_t* errfmt, ...)
{
	if (errfmt)
	{
		va_list ap;
		va_start (ap, errfmt);
		qse_strxvfmt (sys_list->ctx.errmsg, QSE_COUNTOF(sys_list->ctx.errmsg), errfmt, ap);
		va_end (ap);
	}
	else
	{
		qse_strxcpy (sys_list->ctx.errmsg, QSE_COUNTOF(sys_list->ctx.errmsg), qse_awk_rtx_geterrmsg(rtx));
	}
}

static QSE_INLINE void set_errmsg_on_sys_list_with_syserr (qse_awk_rtx_t* rtx, sys_list_t* sys_list)
{
	set_errmsg_on_sys_list (rtx, sys_list, QSE_T("%hs"), strerror(errno));
}


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

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

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

	node->ctx.type = SYS_NODE_DATA_FD;
	node->ctx.u.fd = fd;
	return node;
}

static sys_node_t* new_sys_node_dir (qse_awk_rtx_t* rtx, sys_list_t* list, qse_dir_t* dir)
{
	sys_node_t* node;

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

	node->ctx.type = SYS_NODE_DATA_DIR;
	node->ctx.u.dir = dir;
	return node;
}

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

		case  SYS_NODE_DATA_DIR:
			if (node->ctx.u.dir)
			{
				qse_dir_close (node->ctx.u.dir);
				node->ctx.u.dir = QSE_NULL;
			}
			break;
	}
	__free_sys_node (rtx, list, node);
}

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

static QSE_INLINE sys_list_t* rtx_to_sys_list (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	mod_ctx_t* mctx = (mod_ctx_t*)fi->mod->ctx;
	qse_rbt_pair_t* pair;

	pair = qse_rbt_search(mctx->rtxtab, &rtx, QSE_SIZEOF(rtx));
	QSE_ASSERT (pair != QSE_NULL);
	return (sys_list_t*)QSE_RBT_VPTR(pair);
}

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

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

static sys_node_t* get_sys_list_node_with_arg (qse_awk_rtx_t* rtx, sys_list_t* sys_list, qse_awk_val_t* arg)
{
	qse_awk_int_t id;
	sys_node_t* sys_node;

	if (qse_awk_rtx_valtoint(rtx, arg, &id) <= -1)
	{
		set_errmsg_on_sys_list (rtx, sys_list, QSE_T("illegal instance id"));
		return QSE_NULL;
	}
	else if (!(sys_node = get_sys_list_node(sys_list, id)))
	{
		set_errmsg_on_sys_list (rtx, sys_list, QSE_T("invalid instance id - %zd"), (qse_size_t)id);
		return QSE_NULL;
	}

	return sys_node;
}

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

static int fnc_errmsg (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	qse_awk_val_t* retv;

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

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

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

static int fnc_close (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	int rx = RC_ERROR;
	qse_awk_int_t cflags;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));

	if (qse_awk_rtx_getnargs(rtx) >= 2 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &cflags) <= -1 || cflags < 0)) cflags = 0;

	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_FD)
	{
		/* although free_sys_node can handle other types, sys::close() is allowed to
		 * close nodes of the SYS_NODE_DATA_FD type only */
		if (cflags & CLOSE_KEEPFD)  /* this flag applies to file descriptors only */
		{
			sys_node->ctx.u.fd = -1; /* you may leak the original file descriptor. */
		}

		free_sys_node (rtx, sys_list, sys_node);
		rx = 0;
	}
	else
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

	qse_awk_rtx_setretval (rtx, qse_awk_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 (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	
	qse_awk_int_t rx = RC_ERROR, oflags = 0, mode = DEFAULT_MODE;
	int fd;
	qse_mchar_t* pstr;
	qse_size_t plen;
	qse_awk_val_t* a0;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &oflags) <= -1 || oflags < 0) oflags = O_RDONLY;
	if (qse_awk_rtx_getnargs(rtx) >= 3 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 2), &mode) <= -1 || mode < 0)) mode = DEFAULT_MODE;

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

	a0 = qse_awk_rtx_getarg(rtx, 0);
	pstr = qse_awk_rtx_getvalmbs(rtx, a0, &plen);
	if (!pstr) goto fail;
	fd = open(pstr, oflags, mode);
	qse_awk_rtx_freevalmbs (rtx, a0, pstr);

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

		new_node = new_sys_node_fd(rtx, sys_list, fd);
		if (new_node) 
		{
			rx = new_node->id;
		}
		else 
		{
			close (fd);
		fail:
			rx = awkerr_to_rc(qse_awk_rtx_geterrnum(rtx));
			set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
		}
	}
	else
	{
		rx = syserr_to_rc(errno);
		set_errmsg_on_sys_list_with_syserr (rtx, sys_list);
	}

	QSE_ASSERT (QSE_AWK_IN_QUICKINT_RANGE(rx));
	qse_awk_rtx_setretval (rtx, qse_awk_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 (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	/* wrap a raw system file descriptor into the internal management node */

	sys_list_t* sys_list;
	qse_awk_int_t rx = RC_ERROR;
	qse_awk_int_t fd;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &fd) <= -1)
	{
		rx = awkerr_to_rc(qse_awk_rtx_geterrnum(rtx));
		set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
	}
	else if (fd >= 0 && fd <= QSE_TYPE_MAX(int))
	{
		sys_node_t* sys_node;

		sys_node = new_sys_node_fd(rtx, sys_list, fd);
		if (sys_node) 
		{
			rx = sys_node->id;
		}
		else 
		{
			rx = awkerr_to_rc(qse_awk_rtx_geterrnum(rtx));
			set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
		}
	}
	else
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

	QSE_ASSERT (QSE_AWK_IN_QUICKINT_RANGE(rx));
	qse_awk_rtx_setretval (rtx, qse_awk_rtx_makeintval(rtx, rx));
	return 0;
}


static int fnc_read (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	qse_awk_int_t rx = RC_ERROR;
	qse_awk_int_t reqsize = 8192;

	if (qse_awk_rtx_getnargs(rtx) >= 3 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 2), &reqsize) <= -1 || reqsize <= 0)) reqsize = 8192;
	if (reqsize > QSE_AWK_QUICKINT_MAX) reqsize = QSE_AWK_QUICKINT_MAX;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));
	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_FD)
	{
		if (reqsize > sys_list->ctx.readbuf_capa)
		{
			qse_mchar_t* tmp = qse_awk_rtx_reallocmem(rtx, sys_list->ctx.readbuf, reqsize);
			if (!tmp)
			{
				set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
				goto done;
			}
			sys_list->ctx.readbuf = tmp;
			sys_list->ctx.readbuf_capa = reqsize;
		}

		rx = read(sys_node->ctx.u.fd, sys_list->ctx.readbuf, reqsize);
		if (rx <= 0) 
		{
			if (rx <= -1) 
			{
				rx = syserr_to_rc(errno);
				set_errmsg_on_sys_list_with_syserr(rtx, sys_list);
			}
			goto done;
		}
		else
		{
			qse_awk_val_t* sv;
			int x;

			sv = qse_awk_rtx_makembsval(rtx, sys_list->ctx.readbuf, rx);
			if (!sv) return -1;

			qse_awk_rtx_refupval (rtx, sv);
			x = qse_awk_rtx_setrefval(rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg(rtx, 1), sv);
			qse_awk_rtx_refdownval (rtx, sv);
			if (x <= -1) return -1;
		}
	}
	else 
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

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

static int fnc_write (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	qse_awk_int_t rx = RC_ERROR;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));
	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_FD)
	{
		qse_mchar_t* dptr;
		qse_size_t dlen;
		qse_awk_val_t* a1;

		a1 = qse_awk_rtx_getarg(rtx, 1);
		dptr = qse_awk_rtx_getvalmbs(rtx, a1, &dlen);
		if (dptr)
		{
			rx = write(sys_node->ctx.u.fd, dptr, dlen);
			if (rx <= -1) 
			{
				rx = syserr_to_rc(errno);
				set_errmsg_on_sys_list_with_syserr(rtx, sys_list);
			}
			qse_awk_rtx_freevalmbs (rtx, a1, dptr);
		}
		else
		{
			set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
		}
	}
	else
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

	qse_awk_rtx_setretval (rtx, qse_awk_rtx_makeintval(rtx, rx));
	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 (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node, * sys_node2 = QSE_NULL;
	qse_awk_int_t rx = RC_ERROR;
	qse_awk_int_t oflags = 0;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));
	if (qse_awk_rtx_getnargs(rtx) >= 2)
	{
		sys_node2 = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 1));
		if (!sys_node2 || sys_node2->ctx.type != SYS_NODE_DATA_FD) goto fail_einval;
		if (qse_awk_rtx_getnargs(rtx) >= 3 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 2), &oflags) <= -1 || oflags < 0)) oflags = 0;
	}

	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_FD)
	{
		int fd;

		if (sys_node2)
		{
		#if defined(HAVE_DUP3)
			fd = dup3(sys_node->ctx.u.fd, sys_node2->ctx.u.fd, oflags);
		#else
			fd = dup2(sys_node->ctx.u.fd, sys_node2->ctx.u.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
				sys_node2->ctx.u.fd = fd; /* dup2 or dup3 closes the descriptor implicitly */
				rx = sys_node2->id;
			}
			else
			{
				rx = syserr_to_rc(errno);
				set_errmsg_on_sys_list_with_syserr (rtx, sys_list);
			}
		}
		else
		{
			fd = dup(sys_node->ctx.u.fd);
			if (fd >= 0)
			{
				sys_node_t* new_node;

				new_node = new_sys_node_fd(rtx, sys_list, fd);
				if (new_node) 
				{
					rx = new_node->id;
				}
				else 
				{
					close (fd);
					rx = awkerr_to_rc(qse_awk_rtx_geterrnum(rtx));
					set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
				}
			}
			else
			{
				rx = syserr_to_rc(errno);
				set_errmsg_on_sys_list_with_syserr (rtx, sys_list);
			}
		}
	}
	else
	{
	fail_einval:
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

	qse_awk_rtx_setretval (rtx, qse_awk_rtx_makeintval(rtx, rx));
	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);
	}
*/

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

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

	sys_list = rtx_to_sys_list(rtx, fi);

	if (qse_awk_rtx_getnargs(rtx) >= 3 && (qse_awk_rtx_valtoint(rtx, qse_awk_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)
		{
			qse_awk_val_t* v;
			int x;

			v = qse_awk_rtx_makeintval(rtx, node1->id);
			if (!v) 
			{
			fail:
				free_sys_node (rtx, sys_list, node1);
				free_sys_node (rtx, sys_list, node2);
				return -1;
			}
			qse_awk_rtx_refupval (rtx, v);
			x = qse_awk_rtx_setrefval (rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg(rtx, 0), v);
			qse_awk_rtx_refdownval (rtx, v);
			if (x <= -1) goto fail;

			v = qse_awk_rtx_makeintval(rtx, node2->id);
			if (!v) goto fail;
			qse_awk_rtx_refupval (rtx, v);
			x = qse_awk_rtx_setrefval (rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg(rtx, 1), v);
			qse_awk_rtx_refdownval (rtx, v);
			if (x <= -1) goto fail;

			rx = 0;
		}
		else
		{
			set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
			if (node2) free_sys_node (rtx, sys_list, node2);
			if (node1) free_sys_node (rtx, sys_list, node1);
		}
	}
	else
	{
		rx = syserr_to_rc(errno);
		set_errmsg_on_sys_list_with_syserr (rtx, sys_list);
	}

	qse_awk_rtx_setretval (rtx, qse_awk_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);
        }
*/

static int fnc_opendir (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node = QSE_NULL;
	qse_awk_int_t rx = RC_ERROR, flags = 0;
	qse_char_t* pstr;
	qse_size_t plen;
	qse_awk_val_t* a0;
	qse_dir_t* dir;
	qse_dir_errnum_t oe;

	sys_list = rtx_to_sys_list(rtx, fi);

	if (qse_awk_rtx_getnargs(rtx) >= 2 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &flags) <= -1 || flags < 0)) flags = 0;

	a0 = qse_awk_rtx_getarg(rtx, 0);
	pstr = qse_awk_rtx_getvalstr(rtx, a0, &plen);
	if (!pstr) goto fail;
	dir = qse_dir_open(qse_awk_rtx_getmmgr(rtx), 0, pstr, flags, &oe);
	qse_awk_rtx_freevalstr (rtx, a0, pstr);

	if (dir)
	{
		sys_node = new_sys_node_dir(rtx, sys_list, dir);
		if (sys_node) 
		{
			rx = sys_node->id;
		}
		else 
		{
			qse_dir_close(dir);
		fail:
			set_errmsg_on_sys_list (rtx, sys_list, QSE_NULL);
		}
	}
	else
	{
		rx = direrr_to_rc(oe);
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

	QSE_ASSERT (QSE_AWK_IN_QUICKINT_RANGE(rx));
	qse_awk_rtx_setretval (rtx, qse_awk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_closedir (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	int rx = RC_ERROR;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));
	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_DIR)
	{
		/* although free_sys_node() can handle other types, sys::closedir() is allowed to
		 * close nodes of the SYS_NODE_DATA_DIR type only */
		free_sys_node (rtx, sys_list, sys_node);
		rx = 0;
	}
	else
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

	qse_awk_rtx_setretval (rtx, qse_awk_rtx_makeintval(rtx, rx));
	return 0;
}

static int fnc_readdir (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	qse_awk_int_t rx = RC_ERROR;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));
	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_DIR)
	{
		int y;
		qse_dir_ent_t ent;
		qse_awk_val_t* tmp;

		y = qse_dir_read(sys_node->ctx.u.dir, &ent);
		if (y <= -1) 
		{
			rx = direrr_to_rc(qse_dir_geterrnum(sys_node->ctx.u.dir));
			set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
		}
		else if (y == 0) 
		{
			rx = 0; /* no more entry */
		}
		else
		{
			tmp = qse_awk_rtx_makestrvalwithstr(rtx, ent.name);
			if (!tmp)
			{
				rx = awkerr_to_rc(qse_awk_rtx_geterrnum(rtx));
				set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
			}
			else
			{
				int n;
				qse_awk_rtx_refupval (rtx, tmp);
				n = qse_awk_rtx_setrefval (rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg(rtx, 1), tmp);
				qse_awk_rtx_refdownval (rtx, tmp);
				if (n <= -1) return -1;

				rx = 1; /* has entry */
			}
		}
	}
	else 
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

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

static int fnc_resetdir (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	sys_list_t* sys_list;
	sys_node_t* sys_node;
	qse_awk_int_t rx = RC_ERROR;

	sys_list = rtx_to_sys_list(rtx, fi);
	sys_node = get_sys_list_node_with_arg(rtx, sys_list, qse_awk_rtx_getarg(rtx, 0));

	if (sys_node && sys_node->ctx.type == SYS_NODE_DATA_DIR)
	{
		qse_char_t* path;
		qse_awk_val_t* a1;

		a1 = qse_awk_rtx_getarg(rtx, 1);
		path = qse_awk_rtx_getvalstr(rtx, a1, QSE_NULL);
		if (path)
		{
			if (qse_dir_reset(sys_node->ctx.u.dir, path) <= -1) 
			{
				rx = direrr_to_rc(qse_dir_geterrnum(sys_node->ctx.u.dir));
				set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
			}
			else
			{
				rx = 0; /* success */
				qse_awk_rtx_freevalstr (rtx, a1, path);
			}
		}
		else
		{
			rx = awkerr_to_rc(qse_awk_rtx_geterrnum(rtx));
			set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
		}
	}
	else
	{
		rx = RC_EINVAL;
		set_errmsg_on_sys_list (rtx, sys_list, rc_to_errstr(rx));
	}

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

static int fnc_fork (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t pid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
#elif defined(__OS2__)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
#elif defined(__DOS__)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
#else
	pid = fork();
	if (pid <= -1) 
	{
		pid = syserr_to_rc(errno);
		set_errmsg_on_sys_list_with_syserr (rtx, rtx_to_sys_list(rtx, fi));
	}
#endif

	retv = qse_awk_rtx_makeintval(rtx, pid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_wait (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t pid;
	qse_awk_val_t* retv;
	int rx;
	qse_size_t nargs;
	qse_awk_int_t opts = 0;
	int status;

	nargs = qse_awk_rtx_getnargs(rtx);
	if (nargs >= 3)
	{
		if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 2), &opts) <= -1) return -1;
	}

	rx = qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &pid);
	if (rx >= 0)
	{
#if defined(_WIN32)
		/* TOOD: implement this*/
		rx = RC_ENOIMPL;
		set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(rx));
		status = 0;
#elif defined(__OS2__)
		/* TOOD: implement this*/
		rx = RC_ENOIMPL;
		set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(rx));
		status = 0;
#elif defined(__DOS__)
		/* TOOD: implement this*/
		rx = RC_ENOIMPL;
		set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(rx));
		status = 0;
#else
		rx = waitpid(pid, &status, opts);
		if (rx <= -1) 
		{
			rx = syserr_to_rc(errno);
			set_errmsg_on_sys_list_with_syserr (rtx, rtx_to_sys_list(rtx, fi));
		}
#endif
	}

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

	if (nargs >= 2)
	{
		qse_awk_val_t* sv;
		int x;

		sv = qse_awk_rtx_makeintval(rtx, status);
		if (!sv) return -1;

		qse_awk_rtx_refupval (rtx, sv);
		x = qse_awk_rtx_setrefval(rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg(rtx, 1), sv);
		qse_awk_rtx_refdownval (rtx, sv);
		if (x <= -1)  
		{
			qse_awk_rtx_freemem (rtx, retv);
			return -1;
		}
	}

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_wifexited (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t wstatus;
	qse_awk_val_t* retv;
	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &wstatus) <= -1) return -1;
	retv = qse_awk_rtx_makeintval(rtx, WIFEXITED(wstatus));
	if (!retv) return -1;
	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_wexitstatus (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t wstatus;
	qse_awk_val_t* retv;
	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &wstatus) <= -1) return -1;
	retv = qse_awk_rtx_makeintval(rtx, WEXITSTATUS(wstatus));
	if (!retv) return -1;
	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_wifsignaled (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t wstatus;
	qse_awk_val_t* retv;
	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &wstatus) <= -1) return -1;
	retv = qse_awk_rtx_makeintval(rtx, WIFSIGNALED(wstatus));
	if (!retv) return -1;
	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_wtermsig (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t wstatus;
	qse_awk_val_t* retv;
	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &wstatus) <= -1) return -1;
	retv = qse_awk_rtx_makeintval(rtx, WTERMSIG(wstatus));
	if (!retv) return -1;
	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_wcoredump (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t wstatus;
	qse_awk_val_t* retv;
	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &wstatus) <= -1) return -1;
	retv = qse_awk_rtx_makeintval(rtx, WCOREDUMP(wstatus));
	if (!retv) return -1;
	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_kill (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t pid, sig;
	qse_awk_val_t* retv;
	int rx;

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg (rtx, 0), &pid) <= -1 ||
	    qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg (rtx, 1), &sig) <= -1)
	{
		rx = RC_ERROR;
	}
	else
	{
#if defined(_WIN32)
		/* TOOD: implement this*/
		rx = RC_ENOIMPL;
		set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(rx));
#elif defined(__OS2__)
		/* TOOD: implement this*/
		rx = RC_ENOIMPL;
		set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(rx));
#elif defined(__DOS__)
		/* TOOD: implement this*/
		rx = RC_ENOIMPL;
		set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(rx));
#else
		rx = kill(pid, sig);
		if (rx <= -1) 
		{
			rx = syserr_to_rc(errno);
			set_errmsg_on_sys_list_with_syserr (rtx, rtx_to_sys_list(rtx, fi));
		}
#endif
	}

	retv = qse_awk_rtx_makeintval(rtx, rx);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_getpgid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t pid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
#elif defined(__OS2__)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
#elif defined(__DOS__)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
#else
	/* TODO: support specifing calling process id other than 0 */
	#if defined(HAVE_GETPGID)
	pid = getpgid(0);
	if (pid <= -1) set_errmsg_on_sys_list_with_syserr (rtx, rtx_to_sys_list(rtx, fi));
	#elif defined(HAVE_GETPGRP)
	pid = getpgrp();
	if (pid <= -1) set_errmsg_on_sys_list_with_syserr (rtx, rtx_to_sys_list(rtx, fi));
	#else
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));
	#endif
#endif

	retv = qse_awk_rtx_makeintval(rtx, pid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_getpid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t pid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	pid = GetCurrentProcessId();

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

	pid = (DosGetInfoBlocks(&tib, &pib) == NO_ERROR)? pib->pib_ulpid: -1;

#elif defined(__DOS__)
	/* TOOD: implement this*/
	pid = RC_ENOIMPL;
	set_errmsg_on_sys_list (rtx, rtx_to_sys_list(rtx, fi), rc_to_errstr(pid));

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

	retv = qse_awk_rtx_makeintval (rtx, pid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_gettid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_intptr_t pid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	pid = GetCurrentThreadId();
	
#elif defined(__OS2__)
	PTIB tib;
	PPIB pib;

	pid = (DosGetInfoBlocks (&tib, &pib) == NO_ERROR && tib->tib_ptib2)?
		 tib->tib_ptib2->tib2_ultid: -1;
	
#elif defined(__DOS__)
	/* TOOD: implement this*/
	pid = -1;

#else
	#if defined(SYS_gettid) && defined(QSE_SYSCALL0)
	QSE_SYSCALL0 (pid, SYS_gettid);
	#elif defined(SYS_gettid)
	pid = syscall(SYS_gettid);
	#else
	pid = -1;
	#endif
#endif

	retv = qse_awk_rtx_makeintval (rtx, (qse_awk_int_t)pid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_getppid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t pid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	pid = -1;
	
#elif defined(__OS2__)
	/* TOOD: implement this*/
	pid = -1;
	
#elif defined(__DOS__)
	/* TOOD: implement this*/
	pid = -1;

#else
	pid = getppid ();
#endif

	retv = qse_awk_rtx_makeintval (rtx, pid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_getuid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t uid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	uid = -1;
	
#elif defined(__OS2__)
	/* TOOD: implement this*/
	uid = -1;
	
#elif defined(__DOS__)
	/* TOOD: implement this*/
	uid = -1;

#else
	uid = getuid ();
#endif

	retv = qse_awk_rtx_makeintval (rtx, uid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_getgid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t gid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	gid = -1;
	
#elif defined(__OS2__)
	/* TOOD: implement this*/
	gid = -1;

#elif defined(__DOS__)
	/* TOOD: implement this*/
	gid = -1;

#else
	gid = getgid ();
#endif

	retv = qse_awk_rtx_makeintval (rtx, gid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_geteuid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t uid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	uid = -1;
	
#elif defined(__OS2__)
	/* TOOD: implement this*/
	uid = -1;
	
#elif defined(__DOS__)
	/* TOOD: implement this*/
	uid = -1;

#else
	uid = geteuid ();
#endif

	retv = qse_awk_rtx_makeintval (rtx, uid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_getegid (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t gid;
	qse_awk_val_t* retv;

#if defined(_WIN32)
	/* TOOD: implement this*/
	gid = -1;
	
#elif defined(__OS2__)
	/* TOOD: implement this*/
	gid = -1;
	
#elif defined(__DOS__)
	/* TOOD: implement this*/
	gid = -1;

#else
	gid = getegid ();
#endif

	retv = qse_awk_rtx_makeintval (rtx, gid);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_sleep (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_int_t lv;
	qse_awk_flt_t fv;
	qse_awk_val_t* retv;
	int rx;

	rx = qse_awk_rtx_valtonum(rtx, qse_awk_rtx_getarg (rtx, 0), &lv, &fv);
	if (rx == 0)
	{
#if defined(_WIN32)
		Sleep ((DWORD)QSE_SEC_TO_MSEC(lv));
		rx = 0;
#elif defined(__OS2__)
		DosSleep ((ULONG)QSE_SEC_TO_MSEC(lv));
		rx = 0;
#elif defined(__DOS__)
		#if (defined(__WATCOMC__) && (__WATCOMC__ < 1200))
			sleep (lv);
			rx = 0;
		#else
			rx = sleep (lv);
		#endif
#elif defined(HAVE_NANOSLEEP)
		struct timespec req;
		req.tv_sec = lv;
		req.tv_nsec = 0;
		rx = nanosleep(&req, QSE_NULL);
#else
		rx = sleep(lv);
#endif
	}
	else if (rx >= 1)
	{
#if defined(_WIN32)
		Sleep ((DWORD)QSE_SEC_TO_MSEC(fv));
		rx = 0;
#elif defined(__OS2__)
		DosSleep ((ULONG)QSE_SEC_TO_MSEC(fv));
		rx = 0;
#elif defined(__DOS__)
		/* no high-resolution sleep() is available */
		#if (defined(__WATCOMC__) && (__WATCOMC__ < 1200))
			sleep ((qse_awk_int_t)fv);
			rx = 0;
		#else
			rx = sleep((qse_awk_int_t)fv);
		#endif;
#elif defined(HAVE_NANOSLEEP)
		struct timespec req;
		req.tv_sec = (qse_awk_int_t)fv;
		req.tv_nsec = QSE_SEC_TO_NSEC(fv - req.tv_sec);
		rx = nanosleep(&req, QSE_NULL);
#elif defined(HAVE_SELECT)
		struct timeval req;
		req.tv_sec = (qse_awk_int_t)fv;
		req.tv_usec = QSE_SEC_TO_USEC(fv - req.tv_sec);
		rx = select(0, QSE_NULL, QSE_NULL, QSE_NULL, &req);
#else
		/* no high-resolution sleep() is available */
		rx = sleep((qse_awk_int_t)fv);
#endif
	}

	retv = qse_awk_rtx_makeintval(rtx, rx);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_gettime (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_val_t* retv;
	qse_ntime_t now;

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

	retv = qse_awk_rtx_makeintval (rtx, now.sec);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_settime (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_val_t* retv;
	qse_ntime_t now;
	qse_awk_int_t tmp;
	int rx;

	now.nsec = 0;

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg (rtx, 0), &tmp) <= -1) rx = -1;
	else
	{
		now.sec = tmp;
		if (qse_settime(&now) <= -1) rx = -1;
		else rx = 0;
	}

	retv = qse_awk_rtx_makeintval (rtx, rx);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_mktime (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_ntime_t nt;
	qse_size_t nargs;
	qse_awk_val_t* retv;

	nargs = qse_awk_rtx_getnargs(rtx);
	if (nargs >= 1)
	{
		int sign;
		qse_char_t* str, * p, * end;
		qse_size_t len;
		qse_awk_val_t* a0;
		qse_btime_t bt;

		a0 = qse_awk_rtx_getarg (rtx, 0);
		str = qse_awk_rtx_getvalstr (rtx, a0, &len);
		if (str == QSE_NULL) return -1;

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

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.year = bt.year * 10 + (*p++ - QSE_T('0'));
		bt.year *= sign;
		bt.year -= 1900;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.mon = bt.mon * 10 + (*p++ - QSE_T('0'));
		bt.mon *= sign;
		bt.mon -= 1;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.mday = bt.mday * 10 + (*p++ - QSE_T('0'));
		bt.mday *= sign;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.hour = bt.hour * 10 + (*p++ - QSE_T('0'));
		bt.hour *= sign;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.min = bt.min * 10 + (*p++ - QSE_T('0'));
		bt.min *= sign;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.sec = bt.sec * 10 + (*p++ - QSE_T('0'));
		bt.sec *= sign;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		sign = 1;
		if (p < end && *p == QSE_T('-')) { sign = -1; p++; }
		while (p < end && QSE_ISDIGIT(*p)) bt.isdst = bt.isdst * 10 + (*p++ - QSE_T('0'));
		bt.isdst *= sign;
		while (p < end && (QSE_ISSPACE(*p) || *p == QSE_T('\0'))) p++;

		qse_awk_rtx_freevalstr (rtx, a0, str);
		qse_timelocal (&bt, &nt);
	}
	else
	{
		/* get the current time when no argument is given */
		qse_gettime (&nt);
	}

	retv = qse_awk_rtx_makeintval(rtx, nt.sec);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}


#define STRFTIME_UTC (1 << 0)

static int fnc_strftime (qse_awk_rtx_t* rtx, const qse_awk_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);
	*/

	qse_mchar_t* fmt;
	qse_size_t len;
	qse_awk_val_t* retv;

	fmt = qse_awk_rtx_valtombsdup(rtx, qse_awk_rtx_getarg(rtx, 0), &len);
	if (fmt) 
	{
		qse_ntime_t nt;
		qse_btime_t bt;
		qse_awk_int_t tmpsec, flags = 0;

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

		
		if (qse_awk_rtx_getnargs(rtx) >= 3 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 2), &flags) <= -1 || flags < 0)) flags = 0;

		if (((flags & STRFTIME_UTC)? qse_gmtime(&nt, &bt): qse_localtime(&nt, &bt)) >= 0)
		{
			qse_mchar_t tmpbuf[64], * tmpptr;
			struct tm tm;
			qse_size_t sl;

			QSE_MEMSET (&tm, 0, QSE_SIZEOF(tm));
			tm.tm_year = bt.year;
			tm.tm_mon = bt.mon;
			tm.tm_mday = bt.mday;
			tm.tm_hour = bt.hour;
			tm.tm_min = bt.min;
			tm.tm_sec = bt.sec;
			tm.tm_isdst = bt.isdst;
		#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
			tm.tm_gmtoff = bt.gmtoff;
		#elif defined(HAVE_STRUCT_TM___TM_GMTOFF)
			tm.__tm_gmtoff = bt.gmtoff;
		#endif
			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
			}

			sl = strftime(tmpbuf, QSE_COUNTOF(tmpbuf), fmt, &tm);
			if (sl <= 0 || sl >= QSE_COUNTOF(tmpbuf))
			{
				/* buffer too small */
				qse_mchar_t* tmp;
				qse_size_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] == QSE_MT('%')) 
					{
						count++; /* the nubmer of % specifier */
						i++;
						if (i < len) i++;
					}
					else i++;
				}

				tmpptr = QSE_NULL;
				tmpcapa = QSE_COUNTOF(tmpbuf);
				if (tmpcapa < len) tmpcapa = len;

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

					tmpcapa *= 2;
					tmp = (qse_mchar_t*)qse_awk_rtx_reallocmem(rtx, tmpptr, tmpcapa * QSE_SIZEOF(*tmpptr));
					if (!tmp) 
					{
						if (tmpptr) qse_awk_rtx_freemem (rtx, tmpptr);
						tmpbuf[0] = QSE_MT('\0');
						tmpptr = tmpbuf;
						break;
					}

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

			retv = qse_awk_rtx_makestrvalwithmbs(rtx, tmpptr);
			if (tmpptr && tmpptr != tmpbuf) qse_awk_rtx_freemem (rtx, tmpptr);
			if (retv == QSE_NULL) return -1;

			qse_awk_rtx_setretval (rtx, retv);
		}
		else
		{
			qse_awk_rtx_freemem (rtx, fmt);
		}
	}
	return 0;
}

static int fnc_getenv (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_mchar_t* var;
	qse_size_t len;
	qse_awk_val_t* retv;

	var = qse_awk_rtx_valtombsdup(
		rtx, qse_awk_rtx_getarg (rtx, 0), &len);
	if (var)
	{
		qse_mchar_t* val;

		val = getenv (var);	
		if (val) 
		{
			retv = qse_awk_rtx_makestrvalwithmbs (rtx, val);
			if (retv == QSE_NULL) return -1;

			qse_awk_rtx_setretval (rtx, retv);
		}

		qse_awk_rtx_freemem (rtx, var);
	}

	return 0;
}

static int fnc_getnwifcfg (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_nwifcfg_t cfg;
	qse_awk_rtx_valtostr_out_t out;
	int ret = -1;

	QSE_MEMSET (&cfg, 0, QSE_SIZEOF(cfg));
	out.type = QSE_AWK_RTX_VALTOSTR_CPLCPY;
	out.u.cplcpy.ptr = cfg.name;
	out.u.cplcpy.len = QSE_COUNTOF(cfg.name);
	if (qse_awk_rtx_valtostr(rtx, qse_awk_rtx_getarg(rtx, 0), &out) >= 0)
	{
		qse_awk_int_t type;
		int rx;

		rx = qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &type);
		if (rx >= 0)
		{
			cfg.type = type;

			if (qse_getnwifcfg(&cfg) >= 0)
			{
				/* make a map value containg configuration */
				qse_awk_int_t index, mtu;
				qse_char_t addr[128];
				qse_char_t mask[128];
				qse_char_t ethw[32];
				qse_awk_val_map_data_t md[7];
				qse_awk_val_t* tmp;

				QSE_MEMSET (md, 0, QSE_SIZEOF(md));

				md[0].key.ptr = QSE_T("index");
				md[0].key.len = 5;
				md[0].type = QSE_AWK_VAL_MAP_DATA_INT;
				index = cfg.index;
				md[0].vptr = &index;

				md[1].key.ptr = QSE_T("mtu");
				md[1].key.len = 3;
				md[1].type = QSE_AWK_VAL_MAP_DATA_INT;
				mtu = cfg.mtu;
				md[1].vptr = &mtu;
		
				md[2].key.ptr = QSE_T("addr");
				md[2].key.len = 4;
				md[2].type = QSE_AWK_VAL_MAP_DATA_STR;
				qse_nwadtostr (&cfg.addr, addr, QSE_COUNTOF(addr), QSE_NWADTOSTR_ADDR);
				md[2].vptr = addr;

				md[3].key.ptr = QSE_T("mask");
				md[3].key.len = 4;
				md[3].type = QSE_AWK_VAL_MAP_DATA_STR;
				qse_nwadtostr (&cfg.mask, mask, QSE_COUNTOF(mask), QSE_NWADTOSTR_ADDR);
				md[3].vptr = mask;

				md[4].key.ptr = QSE_T("ethw");
				md[4].key.len = 4;
				md[4].type = QSE_AWK_VAL_MAP_DATA_STR;
				qse_strxfmt (ethw, QSE_COUNTOF(ethw), QSE_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;

				if (cfg.flags & (QSE_NWIFCFG_LINKUP | QSE_NWIFCFG_LINKDOWN))
				{
					md[5].key.ptr = QSE_T("link");
					md[5].key.len = 4;
					md[5].type = QSE_AWK_VAL_MAP_DATA_STR;
					md[5].vptr = (cfg.flags & QSE_NWIFCFG_LINKUP)? QSE_T("up"): QSE_T("down");
				}

				tmp = qse_awk_rtx_makemapvalwithdata (rtx, md);
				if (tmp)
				{
					int x;
					qse_awk_rtx_refupval (rtx, tmp);
					x = qse_awk_rtx_setrefval (rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg(rtx, 2), tmp);
					qse_awk_rtx_refdownval (rtx, tmp);
					if (x <= -1) return -1;
					ret = 0;
				}
			}
		}
	}

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

static int fnc_system (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_val_t* v, * a0;
	qse_char_t* str;
	qse_size_t len;
	int n = 0;

	a0 = qse_awk_rtx_getarg (rtx, 0);
	str = qse_awk_rtx_getvalstr (rtx, a0, &len);
	if (str == QSE_NULL) return -1;

	/* the target name contains a null character.
	 * make system return -1 */
	if (qse_strxchr (str, len, QSE_T('\0')))
	{
		n = -1;
		goto skip_system;
	}

#if defined(_WIN32)
	n = _tsystem (str);
#elif defined(QSE_CHAR_IS_MCHAR)
	n = system (str);
#else

	{
		qse_mchar_t* mbs;
		mbs = qse_wcstombsdupwithcmgr(str, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
		if (mbs == QSE_NULL) 
		{
			n = -1;
			goto skip_system;
		}
		n = system (mbs);
		qse_awk_rtx_freemem (rtx, mbs);
	}

#endif

skip_system:
	qse_awk_rtx_freevalstr (rtx, a0, str);

	v = qse_awk_rtx_makeintval (rtx, (qse_awk_int_t)n);
	if (v == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, v);
	return 0;
}

/* ------------------------------------------------------------ */
static int fnc_chmod (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_val_t* v, * a0;
	qse_char_t* str;
	qse_size_t len;
	int n = 0;
	qse_awk_int_t mode;

	a0 = qse_awk_rtx_getarg (rtx, 0);
	str = qse_awk_rtx_getvalstr (rtx, a0, &len);
	if (!str) return -1;

	/* the target name contains a null character.
	 * make system return -1 */
	if (qse_strxchr(str, len, QSE_T('\0')))
	{
		n = -1;
		goto skip_mkdir;
	}

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &mode) <= -1 || mode < 0) mode = DEFAULT_MODE;

#if defined(_WIN32)
	n = _tchmod(str, mode);
#elif defined(QSE_CHAR_IS_MCHAR)
	n = chmod(str, mode);
#else

	{
		qse_mchar_t* mbs;
		mbs = qse_wcstombsdupwithcmgr(str, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
		if (mbs == QSE_NULL) 
		{
			n = -1;
			goto skip_mkdir;
		}
		n = chmod(mbs, mode);
		qse_awk_rtx_freemem (rtx, mbs);
	}

#endif

skip_mkdir:
	qse_awk_rtx_freevalstr (rtx, a0, str);

	v = qse_awk_rtx_makeintval (rtx, (qse_awk_int_t)n);
	if (v == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, v);
	return 0;
}

static int fnc_mkdir (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_val_t* v, * a0;
	qse_char_t* str;
	qse_size_t len;
	int n = 0;
	qse_awk_int_t mode;

	a0 = qse_awk_rtx_getarg (rtx, 0);
	str = qse_awk_rtx_getvalstr (rtx, a0, &len);
	if (!str) return -1;

	/* the target name contains a null character.
	 * make system return -1 */
	if (qse_strxchr(str, len, QSE_T('\0')))
	{
		n = -1;
		goto skip_mkdir;
	}

	if (qse_awk_rtx_getnargs(rtx) >= 2 && (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &mode) <= -1 || mode < 0)) mode = DEFAULT_MODE;

#if defined(_WIN32)
	n = _tmkdir(str);
#elif defined(QSE_CHAR_IS_MCHAR)
	n = mkdir(str, mode);
#else

	{
		qse_mchar_t* mbs;
		mbs = qse_wcstombsdupwithcmgr(str, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
		if (mbs == QSE_NULL) 
		{
			n = -1;
			goto skip_mkdir;
		}
		n = mkdir(mbs, mode);
		qse_awk_rtx_freemem (rtx, mbs);
	}

#endif

skip_mkdir:
	qse_awk_rtx_freevalstr (rtx, a0, str);

	v = qse_awk_rtx_makeintval (rtx, (qse_awk_int_t)n);
	if (v == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, v);
	return 0;
}

static int fnc_unlink (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	qse_awk_val_t* v, * a0;
	qse_char_t* str;
	qse_size_t len;
	int n = 0;

	a0 = qse_awk_rtx_getarg (rtx, 0);
	str = qse_awk_rtx_getvalstr(rtx, a0, &len);
	if (!str) return -1;

	/* the target name contains a null character.
	 * make system return -1 */
	if (qse_strxchr(str, len, QSE_T('\0')))
	{
		n = -1;
		goto skip_unlink;
	}

#if defined(_WIN32)
	n = _tunlink(str);
#elif defined(QSE_CHAR_IS_MCHAR)
	n = unlink(str);
#else

	{
		qse_mchar_t* mbs;
		mbs = qse_wcstombsdupwithcmgr(str, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
		if (mbs == QSE_NULL) 
		{
			n = -1;
			goto skip_unlink;
		}
		n = unlink(mbs);
		qse_awk_rtx_freemem (rtx, mbs);
	}

#endif

skip_unlink:
	qse_awk_rtx_freevalstr (rtx, a0, str);

	v = qse_awk_rtx_makeintval (rtx, (qse_awk_int_t)n);
	if (v == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, v);
	return 0;
}

/* ------------------------------------------------------------ */
static void open_remote_log_socket (qse_awk_rtx_t* rtx, mod_ctx_t* mctx)
{
#if defined(_WIN32)
	/* TODO: implement this */
#else
	int sck, flags;
	int domain = qse_skadfamily(&mctx->log.skad);
	int type = SOCK_DGRAM;

	QSE_ASSERT (mctx->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:
	mctx->log.sck = sck;

#endif
}

static int fnc_openlog (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	int rx = -1;
	qse_awk_int_t opt, fac;
	qse_awk_val_t* retv;
	qse_char_t* ident = QSE_NULL, * actual_ident;
	qse_size_t ident_len;
	qse_mchar_t* mbs_ident;
	mod_ctx_t* mctx = (mod_ctx_t*)fi->mod->ctx;
	qse_nwad_t nwad;
	syslog_type_t log_type = SYSLOG_LOCAL;


	ident = qse_awk_rtx_getvalstr(rtx, qse_awk_rtx_getarg(rtx, 0), &ident_len);
	if (!ident) goto done;

	/* the target name contains a null character.
	 * make system return -1 */
	if (qse_strxchr(ident, ident_len, QSE_T('\0'))) goto done;

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 1), &opt) <= -1) goto done;
	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 2), &fac) <= -1) goto done;

	if (qse_strbeg(ident, QSE_T("remote://")))
	{
		qse_char_t* slash;
		/* "udp://remote-addr:remote-port/syslog-identifier" */

		log_type = SYSLOG_REMOTE;
		actual_ident = ident + 9;
		slash = qse_strchr(actual_ident, QSE_T('/'));
		if (!slash) goto done;
		if (qse_strntonwad(actual_ident, slash - actual_ident, &nwad) <= -1) goto done;
		actual_ident = slash + 1;
	}
	else if (qse_strbeg(ident, QSE_T("local://")))
	{
		/* "local://syslog-identifier" */
		actual_ident = ident + 8;
	}
	else
	{
		actual_ident = ident;
	}

#if defined(QSE_CHAR_IS_MCHAR)
	mbs_ident = qse_mbsdup(actual_ident, qse_awk_rtx_getmmgr(rtx));
#else
	mbs_ident = qse_wcstombsdupwithcmgr(actual_ident, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
#endif
	if (!mbs_ident) goto done;

	if (mctx->log.ident) qse_awk_rtx_freemem (rtx, mctx->log.ident);
	mctx->log.ident = mbs_ident;

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

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

	rx = 0;

done:
	if (ident) qse_awk_rtx_freevalstr(rtx, qse_awk_rtx_getarg(rtx, 0), ident);

	retv = qse_awk_rtx_makeintval(rtx, rx);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

static int fnc_closelog (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	int rx = -1;
	qse_awk_val_t* retv;
	mod_ctx_t* mctx = (mod_ctx_t*)fi->mod->ctx;

	switch (mctx->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). */
			mctx->log.syslog_opened = 0;
		#endif
			break;

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

			if (mctx->log.dmsgbuf)
			{
				qse_mbs_close (mctx->log.dmsgbuf);
				mctx->log.dmsgbuf = QSE_NULL;
			}

			break;
	}

	if (mctx->log.ident)
	{
		qse_awk_rtx_freemem (rtx, mctx->log.ident);
		mctx->log.ident = QSE_NULL;
	}

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

	rx = 0;

	retv = qse_awk_rtx_makeintval(rtx, rx);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}


static int fnc_writelog (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	int rx = -1;
	qse_awk_val_t* retv;
	qse_awk_int_t pri;
	qse_char_t* msg = QSE_NULL;
	qse_size_t msglen;
	mod_ctx_t* mctx = (mod_ctx_t*)fi->mod->ctx;

	if (qse_awk_rtx_valtoint(rtx, qse_awk_rtx_getarg(rtx, 0), &pri) <= -1) goto done;

	msg = qse_awk_rtx_getvalstr(rtx, qse_awk_rtx_getarg(rtx, 1), &msglen);
	if (!msg) goto done;

	if (qse_strxchr(msg, msglen, QSE_T('\0'))) goto done;

	if (mctx->log.type == SYSLOG_LOCAL)
	{
	#if defined(ENABLE_SYSLOG)
		#if defined(QSE_CHAR_IS_MCHAR)
		syslog(pri, "%s", msg);
		#else
		{
			qse_mchar_t* mbs;
			mbs = qse_wcstombsdupwithcmgr(msg, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
			if (!mbs) goto done;
			syslog(pri, "%s", mbs);
			qse_awk_rtx_freemem (rtx, mbs);
		}
		#endif
	#endif
	}
	else if (mctx->log.type == SYSLOG_REMOTE)
	{
	#if defined(_WIN32)
		/* TODO: implement this */
	#else
		qse_ntime_t now;
		qse_btime_t cnow;

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

		if (mctx->log.sck <= -1) open_remote_log_socket (rtx, mctx);

		if (mctx->log.sck >= 0)
		{
			if (!mctx->log.dmsgbuf) mctx->log.dmsgbuf = qse_mbs_open(qse_awk_rtx_getmmgr(rtx), 0, 0);
			if (!mctx->log.dmsgbuf) goto done;

			if (qse_gettime(&now) <= -1 || qse_localtime(&now, &cnow) <= -1) goto done;

			if (qse_mbs_fmt (
				mctx->log.dmsgbuf, QSE_MT("<%d>%s %02d %02d:%02d:%02d "), 
				(int)(mctx->log.fac | pri),
				__syslog_month_names[cnow.mon], cnow.mday, 
				cnow.hour, cnow.min, cnow.sec) == (qse_size_t)-1) goto done;

			if (mctx->log.ident || (mctx->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 (qse_mbs_fcat(mctx->log.dmsgbuf, QSE_MT("%hs"), (mctx->log.ident? mctx->log.ident: QSE_MT(""))) == (qse_size_t)-1) goto done;

				if (mctx->log.opt & LOG_PID)
				{
					if (qse_mbs_fcat(mctx->log.dmsgbuf, QSE_MT("[%d]"), (int)QSE_GETPID()) == (qse_size_t)-1) goto done;
				}

				if (qse_mbs_fcat(mctx->log.dmsgbuf, QSE_MT(": ")) == (qse_size_t)-1) goto done;
			}

		#if defined(QSE_CHAR_IS_MCHAR)
			if (qse_mbs_fcat(mctx->log.dmsgbuf, QSE_MT("%hs"), msg) == (qse_size_t)-1) goto done;
		#else
			if (qse_mbs_fcat(mctx->log.dmsgbuf, QSE_MT("%ls"), msg) == (qse_size_t)-1) goto done;
		#endif

			/* don't care about output failure */
			sendto (mctx->log.sck, QSE_MBS_PTR(mctx->log.dmsgbuf), QSE_MBS_LEN(mctx->log.dmsgbuf),
			        0, (struct sockaddr*)&mctx->log.skad, qse_skadsize(&mctx->log.skad));
		}
	#endif
	}

	rx = 0;

done:
	if (msg) qse_awk_rtx_freevalstr(rtx, qse_awk_rtx_getarg(rtx, 1), msg);

	retv = qse_awk_rtx_makeintval(rtx, rx);
	if (retv == QSE_NULL) return -1;

	qse_awk_rtx_setretval (rtx, retv);
	return 0;
}

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

typedef struct fnctab_t fnctab_t;
struct fnctab_t
{
	const qse_char_t* name;
	qse_awk_mod_sym_fnc_t info;
};

typedef struct inttab_t inttab_t;
struct inttab_t
{
	const qse_char_t* name;
	qse_awk_mod_sym_int_t info;
};

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

	{ QSE_T("WCOREDUMP"),   { { 1, 1, QSE_NULL     }, fnc_wcoredump,   0  } },
	{ QSE_T("WEXITSTATUS"), { { 1, 1, QSE_NULL     }, fnc_wexitstatus, 0  } },
	{ QSE_T("WIFEXITED"),   { { 1, 1, QSE_NULL     }, fnc_wifexited,   0  } },
	{ QSE_T("WIFSIGNALED"), { { 1, 1, QSE_NULL     }, fnc_wifsignaled, 0  } },
	{ QSE_T("WTERMSIG"),    { { 1, 1, QSE_NULL     }, fnc_wtermsig,    0  } },
	{ QSE_T("chmod"),       { { 2, 2, QSE_NULL     }, fnc_chmod,       0  } },
	{ QSE_T("close"),       { { 1, 2, QSE_NULL     }, fnc_close,       0  } },
	{ QSE_T("closedir"),    { { 1, 1, QSE_NULL     }, fnc_closedir,    0  } },
	{ QSE_T("closelog"),    { { 0, 0, QSE_NULL     }, fnc_closelog,    0  } },
	{ QSE_T("dup"),         { { 1, 3, QSE_NULL     }, fnc_dup,         0  } },
	{ QSE_T("errmsg"),      { { 0, 0, QSE_NULL     }, fnc_errmsg,      0  } },
	{ QSE_T("fork"),        { { 0, 0, QSE_NULL     }, fnc_fork,        0  } },
	{ QSE_T("getegid"),     { { 0, 0, QSE_NULL     }, fnc_getegid,     0  } },
	{ QSE_T("getenv"),      { { 1, 1, QSE_NULL     }, fnc_getenv,      0  } },
	{ QSE_T("geteuid"),     { { 0, 0, QSE_NULL     }, fnc_geteuid,     0  } },
	{ QSE_T("getgid"),      { { 0, 0, QSE_NULL     }, fnc_getgid,      0  } },
	{ QSE_T("getnwifcfg"),  { { 3, 3, QSE_T("vvr") }, fnc_getnwifcfg,  0  } },
	{ QSE_T("getpgid"),     { { 0, 0, QSE_NULL     }, fnc_getpgid,     0  } },
	{ QSE_T("getpid"),      { { 0, 0, QSE_NULL     }, fnc_getpid,      0  } },
	{ QSE_T("getppid"),     { { 0, 0, QSE_NULL     }, fnc_getppid,     0  } },
	{ QSE_T("gettid"),      { { 0, 0, QSE_NULL     }, fnc_gettid,      0  } },
	{ QSE_T("gettime"),     { { 0, 0, QSE_NULL     }, fnc_gettime,     0  } },
	{ QSE_T("getuid"),      { { 0, 0, QSE_NULL     }, fnc_getuid,      0  } },
	{ QSE_T("kill"),        { { 2, 2, QSE_NULL     }, fnc_kill,        0  } },
	{ QSE_T("mkdir"),       { { 1, 2, QSE_NULL     }, fnc_mkdir,       0  } },
	{ QSE_T("mktime"),      { { 0, 1, QSE_NULL     }, fnc_mktime,      0  } },
	{ QSE_T("open"),        { { 2, 3, QSE_NULL     }, fnc_open,        0  } },
	{ QSE_T("opendir"),     { { 1, 2, QSE_NULL     }, fnc_opendir,     0  } },
	{ QSE_T("openfd"),      { { 1, 1, QSE_NULL     }, fnc_openfd,      0  } },
	{ QSE_T("openlog"),     { { 3, 3, QSE_NULL     }, fnc_openlog,     0  } },
	{ QSE_T("pipe"),        { { 2, 3, QSE_T("rrv") }, fnc_pipe,        0  } },
	{ QSE_T("read"),        { { 2, 3, QSE_T("vrv") }, fnc_read,        0  } },
	{ QSE_T("readdir"),     { { 2, 2, QSE_T("vr")  }, fnc_readdir,     0  } },
	{ QSE_T("resetdir"),    { { 2, 2, QSE_NULL     }, fnc_resetdir,    0  } },
	{ QSE_T("settime"),     { { 1, 1, QSE_NULL     }, fnc_settime,     0  } },
	{ QSE_T("sleep"),       { { 1, 1, QSE_NULL     }, fnc_sleep,       0  } },
	{ QSE_T("strftime"),    { { 2, 3, QSE_NULL     }, fnc_strftime,    0  } },
	{ QSE_T("system"),      { { 1, 1, QSE_NULL     }, fnc_system,      0  } },
	{ QSE_T("systime"),     { { 0, 0, QSE_NULL     }, fnc_gettime,     0  } }, /* alias to gettime() */
	{ QSE_T("unlink"),      { { 1, 1, QSE_NULL     }, fnc_unlink,      0  } },
	{ QSE_T("wait"),        { { 1, 3, QSE_T("vrv") }, fnc_wait,        0  } },
	{ QSE_T("write"),       { { 2, 2, QSE_NULL     }, fnc_write,       0  } },
	{ QSE_T("writelog"),    { { 2, 2, QSE_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 inttab_t inttab[] =
{
	/* keep this table sorted for binary search in query(). */
	{ QSE_T("C_KEEPFD"),           { CLOSE_KEEPFD } },

	{ QSE_T("DIR_SORT"),           { QSE_DIR_SORT } },

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

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

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

	{ QSE_T("NWIFCFG_IN4"), { QSE_NWIFCFG_IN4 } },
	{ QSE_T("NWIFCFG_IN6"), { QSE_NWIFCFG_IN6 } },

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

	{ QSE_T("RC_EAGAIN"),  { RC_EAGAIN } },
	{ QSE_T("RC_EBADF"),   { RC_EBADF } },
	{ QSE_T("RC_ECHILD"),  { RC_ECHILD } },
	{ QSE_T("RC_EEXIST"),  { RC_EEXIST } },
	{ QSE_T("RC_EINTR"),   { RC_EINTR } },
	{ QSE_T("RC_EINVAL"),  { RC_EINVAL } },
	{ QSE_T("RC_ENOENT"),  { RC_ENOENT } },
	{ QSE_T("RC_ENOIMPL"), { RC_ENOIMPL } },
	{ QSE_T("RC_ENOMEM"),  { RC_ENOMEM } },
	{ QSE_T("RC_ENOTDIR"), { RC_ENOTDIR } },
	{ QSE_T("RC_ENOSYS"),  { RC_ENOSYS } },
	{ QSE_T("RC_EPERM"),   { RC_EPERM } },
	{ QSE_T("RC_ERROR"),   { RC_ERROR } },

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

	{ QSE_T("STRFTIME_UTC"), { STRFTIME_UTC } },

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

static int query (qse_awk_mod_t* mod, qse_awk_t* awk, const qse_char_t* name, qse_awk_mod_sym_t* sym)
{
	qse_cstr_t ea;
	int left, right, mid, n;

	left = 0; right = QSE_COUNTOF(fnctab) - 1;

	while (left <= right)
	{
		mid = left + (right - left) / 2;

		n = qse_strcmp (fnctab[mid].name, name);
		if (n > 0) right = mid - 1; 
		else if (n < 0) left = mid + 1;
		else
		{
			sym->type = QSE_AWK_MOD_FNC;
			sym->u.fnc = fnctab[mid].info;
			return 0;
		}
	}

	left = 0; right = QSE_COUNTOF(inttab) - 1;
	while (left <= right)
	{
		mid = left + (right - left) / 2;

		n = qse_strcmp (inttab[mid].name, name);
		if (n > 0) right = mid - 1; 
		else if (n < 0) left = mid + 1;
		else
		{
			sym->type = QSE_AWK_MOD_INT;
			sym->u.in = inttab[mid].info;
			return 0;
		}
	}

	ea.ptr = (qse_char_t*)name;
	ea.len = qse_strlen(name);
	qse_awk_seterror (awk, QSE_AWK_ENOENT, &ea, QSE_NULL);
	return -1;
}

/* TODO: proper resource management */

static int init (qse_awk_mod_t* mod, qse_awk_rtx_t* rtx)
{
	mod_ctx_t* mctx = (mod_ctx_t*)mod->ctx;
	rtx_data_t data;

	mctx->log.type = SYSLOG_LOCAL;
	mctx->log.syslog_opened = 0;
	mctx->log.sck = -1;

	QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
	if (qse_rbt_insert(mctx->rtxtab, &rtx, QSE_SIZEOF(rtx), &data, QSE_SIZEOF(data)) == QSE_NULL) 
	{
		qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL);
		return -1;
	}

	return 0;
}

static void fini (qse_awk_mod_t* mod, qse_awk_rtx_t* rtx)
{
	/* TODO: 
	for (each pid for rtx) kill (pid, SIGKILL);
	for (each pid for rtx) waitpid (pid, QSE_NULL, 0);
	*/

	mod_ctx_t* mctx = (mod_ctx_t*)mod->ctx;
	qse_rbt_pair_t* pair;

	/* garbage clean-up */
	pair = qse_rbt_search(mctx->rtxtab, &rtx, QSE_SIZEOF(rtx));
	if (pair)
	{
		rtx_data_t* data;
		sys_node_t* sys_node, * sys_next;

		data = (rtx_data_t*)QSE_RBT_VPTR(pair);

		sys_node = data->sys_list.head;
		while (sys_node)
		{
			sys_next = sys_node->next;
			free_sys_node (rtx, &data->sys_list, sys_node);
			sys_node = sys_next;
		}

		if (data->sys_list.ctx.readbuf)
		{
			qse_awk_rtx_freemem (rtx, data->sys_list.ctx.readbuf);
			data->sys_list.ctx.readbuf = QSE_NULL;
			data->sys_list.ctx.readbuf_capa = 0;
		}
		qse_rbt_delete (mctx->rtxtab, &rtx, QSE_SIZEOF(rtx));
	}


#if defined(ENABLE_SYSLOG)
	if (mctx->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 ();
		mctx->log.syslog_opened = 0;
	}
#endif
	
	if (mctx->log.sck >= 0)
	{
	#if defined(_WIN32)
		/* TODO: implement this */
	#else
		close (mctx->log.sck);
	#endif
		mctx->log.sck = -1;
	}

	if (mctx->log.dmsgbuf)
	{
		qse_mbs_close (mctx->log.dmsgbuf);
		mctx->log.dmsgbuf = QSE_NULL;
	}

	if (mctx->log.ident) 
	{
		qse_awk_rtx_freemem (rtx, mctx->log.ident);
		mctx->log.ident = QSE_NULL;
	}
}

static void unload (qse_awk_mod_t* mod, qse_awk_t* awk)
{
	mod_ctx_t* mctx = (mod_ctx_t*)mod->ctx;

	QSE_ASSERT (QSE_RBT_SIZE(mctx->rtxtab) == 0);
	qse_rbt_close (mctx->rtxtab);

	qse_awk_freemem (awk, mctx);
}

int qse_awk_mod_sys (qse_awk_mod_t* mod, qse_awk_t* awk)
{
	qse_rbt_t* rbt;

	mod->query = query;
	mod->unload = unload;

	mod->init = init;
	mod->fini = fini;

	mod->ctx = qse_awk_callocmem(awk, QSE_SIZEOF(mod_ctx_t));
	if (!mod->ctx) return -1;

	rbt = qse_rbt_open(qse_awk_getmmgr(awk), 0, 1, 1);
	if (rbt == QSE_NULL) 
	{
		qse_awk_freemem (awk, mod->ctx);
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return -1;
	}
	qse_rbt_setstyle (rbt, qse_getrbtstyle(QSE_RBT_STYLE_INLINE_COPIERS));

	((mod_ctx_t*)mod->ctx)->rtxtab = rbt;
	return 0;
}
