/*
 * FixScript IO v0.4 - http://www.fixscript.org/
 * Copyright (c) 2019-2021 Martin Dvorak <jezek2@advel.cz>
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

// ZLIB code available at http://public-domain.advel.cz/ under CC0 license

#if defined(FIXBUILD_BINCOMPAT) && defined(__linux__)
#define fcntl fcntl__64
#endif

#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#ifdef _WIN32
#define UNICODE
#define _UNICODE
#include <winsock2.h>
#include <mswsock.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <pthread.h>
#include <signal.h>
#ifdef __linux__
#include <sys/epoll.h>
#else
#include <sys/poll.h>
#endif
#include <time.h>
#include <sys/time.h>
#include <sys/wait.h>
#endif
#ifdef __APPLE__
#include <mach/mach_time.h>
#endif
#include "fixio.h"

#if defined(FIXBUILD_BINCOMPAT) && defined(__linux__)
   #undef fcntl
   extern int fcntl(int fd, int cmd, ...);

   #if defined(__i386__)
      asm(".symver fcntl,fcntl@GLIBC_2.0");
   #elif defined(__x86_64__)
      asm(".symver memcpy,memcpy@GLIBC_2.2.5");
   #elif defined(__arm__)
   #endif
#endif

enum {
   ASYNC_READ  = 1 << 0,
   ASYNC_WRITE = 1 << 1
};

#if defined(__linux__)
#define USE_EPOLL
#elif !defined(_WIN32)
#define USE_POLL
#endif

#if defined(USE_EPOLL)

typedef struct {
   int epoll_fd;
   int pipe_read_fd;
   int pipe_write_fd;
   struct epoll_event events[32];
   int cur, cnt;
} Poll;

static Poll *poll_create()
{
   Poll *poll;
   struct epoll_event event;
   int fds[2], flags;

   poll = calloc(1, sizeof(Poll));
   poll->epoll_fd = epoll_create(4);
   if (poll->epoll_fd == -1) {
      free(poll);
      return NULL;
   }

   if (pipe(fds) != 0) {
      close(poll->epoll_fd);
      free(poll);
      return NULL;
   }
   poll->pipe_read_fd = fds[0];
   poll->pipe_write_fd = fds[1];
   flags = fcntl(poll->pipe_read_fd, F_GETFL);
   flags |= O_NONBLOCK;
   fcntl(poll->pipe_read_fd, F_SETFL, flags);
   
   event.events = EPOLLIN;
   event.data.ptr = NULL;
   if (epoll_ctl(poll->epoll_fd, EPOLL_CTL_ADD, poll->pipe_read_fd, &event) != 0) {
      close(poll->pipe_read_fd);
      close(poll->pipe_write_fd);
      close(poll->epoll_fd);
      free(poll);
      return NULL;
   }
   return poll;
}

static void poll_destroy(Poll *poll)
{
   close(poll->pipe_read_fd);
   close(poll->pipe_write_fd);
   close(poll->epoll_fd);
   free(poll);
}

static int poll_add_socket(Poll *poll, int fd, void *data, int mode)
{
   struct epoll_event event;
   
   event.events = 0;
   if (mode & ASYNC_READ) event.events |= EPOLLIN;
   if (mode & ASYNC_WRITE) event.events |= EPOLLOUT;
   event.data.ptr = data;
   return epoll_ctl(poll->epoll_fd, EPOLL_CTL_ADD, fd, &event) == 0;
}

static int poll_remove_socket(Poll *poll, int fd)
{
   struct epoll_event event;
   
   return epoll_ctl(poll->epoll_fd, EPOLL_CTL_DEL, fd, &event) == 0;
}

static int poll_update_socket(Poll *poll, int fd, void *data, int mode)
{
   struct epoll_event event;
   
   event.events = 0;
   if (mode & ASYNC_READ) event.events |= EPOLLIN;
   if (mode & ASYNC_WRITE) event.events |= EPOLLOUT;
   event.data.ptr = data;
   return epoll_ctl(poll->epoll_fd, EPOLL_CTL_MOD, fd, &event) == 0;
}

static void poll_interrupt(Poll *poll)
{
   char c;

   while (write(poll->pipe_write_fd, &c, 1) == 0);
}

static void poll_wait(Poll *poll, int timeout)
{
   if (poll->cur < poll->cnt) return;

   poll->cur = 0;
   poll->cnt = epoll_wait(poll->epoll_fd, poll->events, sizeof(poll->events)/sizeof(struct epoll_event), timeout);
   if (poll->cnt < 0) {
      poll->cnt = 0;
   }
}

static void *poll_get_event(Poll *poll, int *flags)
{
   struct epoll_event *event;
   char c;
   
   for (;;) {
      if (poll->cur >= poll->cnt) return NULL;

      event = &poll->events[poll->cur++];
      if (!event->data.ptr && (event->events & EPOLLIN)) {
         while (read(poll->pipe_read_fd, &c, 1) == 1);
         continue;
      }

      *flags = 0;
      if (event->events & EPOLLIN) *flags |= ASYNC_READ;
      if (event->events & EPOLLOUT) *flags |= ASYNC_WRITE;
      return event->data.ptr;
   }
}

#elif defined(USE_POLL)

typedef struct {
   int epoll_fd;
   int pipe_read_fd;
   int pipe_write_fd;
   struct pollfd *fds;
   void **data;
   int cap, cnt;
   int pending;
} Poll;

static Poll *poll_create()
{
   Poll *poll;
   int fds[2], flags;

   poll = calloc(1, sizeof(Poll));
   poll->cap = 4;
   poll->fds = malloc(poll->cap * sizeof(struct pollfd));
   poll->data = malloc(poll->cap * sizeof(void *));
   if (!poll->fds || !poll->data) {
      free(poll->fds);
      free(poll->data);
      free(poll);
      return NULL;
   }

   if (pipe(fds) != 0) {
      free(poll->fds);
      free(poll->data);
      free(poll);
      return NULL;
   }
   poll->pipe_read_fd = fds[0];
   poll->pipe_write_fd = fds[1];
   flags = fcntl(poll->pipe_read_fd, F_GETFL);
   flags |= O_NONBLOCK;
   fcntl(poll->pipe_read_fd, F_SETFL, flags);

   poll->cnt = 1;
   poll->fds[0].fd = poll->pipe_read_fd;
   poll->fds[0].events = POLLIN;
   poll->data[0] = NULL;
   return poll;
}

static void poll_destroy(Poll *poll)
{
   close(poll->pipe_read_fd);
   close(poll->pipe_write_fd);
   free(poll->fds);
   free(poll->data);
   free(poll);
}

static int poll_add_socket(Poll *poll, int fd, void *data, int mode)
{
   struct pollfd *new_fds;
   void **new_data;
   int i, new_cap;

   for (i=0; i<poll->cnt; i++) {
      if (poll->fds[i].fd == fd) return 0;
   }

   if (poll->cnt == poll->cap) {
      if (poll->cap > (1<<26)) {
         return 0;
      }
      new_cap = poll->cap << 1;
      new_fds = realloc(poll->fds, new_cap * sizeof(struct pollfd));
      if (!new_fds) {
         return 0;
      }
      poll->fds = new_fds;
      new_data = realloc(poll->data, new_cap * sizeof(void *));
      if (!new_data) {
         return 0;
      }
      poll->data = new_data;
      poll->cap = new_cap;
   }

   poll->fds[poll->cnt].fd = fd;
   poll->fds[poll->cnt].events = 0;
   if (mode & ASYNC_READ) poll->fds[poll->cnt].events |= POLLIN;
   if (mode & ASYNC_WRITE) poll->fds[poll->cnt].events |= POLLOUT;
   poll->data[poll->cnt] = data;
   poll->cnt++;
   return 1;
}

static int poll_remove_socket(Poll *poll, int fd)
{
   int i;

   for (i=0; i<poll->cnt; i++) {
      if (poll->fds[i].fd == fd) {
         poll->fds[i] = poll->fds[poll->cnt-1];
         poll->data[i] = poll->data[poll->cnt-1];
         poll->cnt--;
         return 1;
      }
   }
   return 0;
}

static int poll_update_socket(Poll *poll, int fd, void *data, int mode)
{
   int i;

   for (i=0; i<poll->cnt; i++) {
      if (poll->fds[i].fd == fd) {
         poll->fds[i].events = 0;
         if (mode & ASYNC_READ) poll->fds[i].events |= POLLIN;
         if (mode & ASYNC_WRITE) poll->fds[i].events |= POLLOUT;
         poll->data[i] = data;
         return 1;
      }
   }
   return 0;
}

static void poll_interrupt(Poll *poll)
{
   char c;

   while (write(poll->pipe_write_fd, &c, 1) == 0);
}

static void poll_wait(Poll *_poll, int timeout)
{
   if (_poll->pending > 0) return;

   _poll->pending = poll(_poll->fds, _poll->cnt, timeout);
   if (_poll->pending < 0) {
      _poll->pending = 0;
   }
}

static void *poll_get_event(Poll *poll, int *flags)
{
   int i;
   char c;
   
   if (poll->pending == 0) return NULL;

   for (i=0; i<poll->cnt; i++) {
      if (!poll->data[i] && (poll->fds[i].revents & POLLIN)) {
         while (read(poll->pipe_read_fd, &c, 1) == 1);
         if (--poll->pending == 0) return NULL;
         continue;
      }

      if (poll->fds[i].revents & (POLLIN | POLLOUT)) {
         *flags = 0;
         if (poll->fds[i].revents & POLLIN) *flags |= ASYNC_READ;
         if (poll->fds[i].revents & POLLOUT) *flags |= ASYNC_WRITE;
         poll->pending--;
         return poll->data[i];
      }
   }

   return NULL;
}

#endif

#ifdef _WIN32
#define ETIMEDOUT -1000
typedef CRITICAL_SECTION pthread_mutex_t;
typedef HANDLE pthread_cond_t;
#endif

enum {
   ZC_GZIP     = 1 << 0,
   ZC_COMPRESS = 1 << 1
};

enum {
   GZIP_FTEXT    = 1 << 0,
   GZIP_FHCRC    = 1 << 1,
   GZIP_FEXTRA   = 1 << 2,
   GZIP_FNAME    = 1 << 3,
   GZIP_FCOMMENT = 1 << 4
};

enum {
   COMP_ERROR = -1,
   COMP_DONE  = 0,
   COMP_FLUSH = 1,
   COMP_MORE  = 2
};

enum {
   TYPE_FILE      = 0,
   TYPE_DIRECTORY = 1,
   TYPE_SPECIAL   = 2,
   TYPE_SYMLINK   = 0x80
};

enum {
   SCRIPT_FILE_READ   = 0x01,
   SCRIPT_FILE_WRITE  = 0x02,
   SCRIPT_FILE_CREATE = 0x04
};

enum {
   EVENT_READ  = 0x01,
   EVENT_WRITE = 0x02
};

enum {
   ASYNC_TCP_CONNECTION,
   ASYNC_TCP_SERVER
};

enum {
   REDIR_IN        = 0x01,
   REDIR_OUT       = 0x02,
   REDIR_ERR       = 0x04,
   REDIR_MERGE_ERR = 0x08
};

typedef struct {
   const unsigned char *src, *src_end;
   unsigned char *dest, *dest_end;
} ZCommon;

// note: the source buffer (not the current pointers) must be able to store at least 258 bytes
#define ZCOMP_NUM_BUCKETS 4096 // 4096*8*2 = 64KB
#define ZCOMP_NUM_SLOTS   8
typedef struct {
   const unsigned char *src, *src_end;
   unsigned char *dest, *dest_end;
   int src_final, src_flush;
   int flushable;

   int state;
   uint32_t bits;
   int num_bits;
   unsigned char extra[7];
   int extra_len;

   unsigned char circular[32768];
   int circular_pos, circular_written;
   unsigned short hash[ZCOMP_NUM_BUCKETS * ZCOMP_NUM_SLOTS];
} ZCompress;

// note: the source buffer (not the current pointers) must be able to store at least 570 bytes
typedef struct {
   const unsigned char *src, *src_end;
   unsigned char *dest, *dest_end;

   int state;
   uint32_t bits;
   int num_bits;
   int final_block;
   int remaining, dist;

   uint16_t lit_symbols[288], lit_counts[16];
   uint8_t dist_symbols[32], dist_counts[16];

   char circular[32768];
   int circular_pos, circular_written;
} ZUncompress;

typedef struct {
   ZCompress z;
   int state;
   unsigned char extra[10];
   int extra_len;
   uint32_t crc;
   uint32_t in_size;
} GZipCompress;

typedef struct {
   ZUncompress z;
   int state;
   int flags;
   int remaining;
   uint32_t crc;
   uint32_t out_size;
} GZipUncompress;

typedef struct {
   Heap *heap;
   void *state;
   int size;
   int read, written;
} CompressHandle;

typedef struct {
   volatile int refcnt;
   volatile int closed;
#if defined(_WIN32)
   HANDLE handle;
#else
   int fd;
#endif
   int mode;
} FileHandle;

typedef struct {
   volatile int refcnt;
   volatile int closed;
#if defined(_WIN32)
   SOCKET socket;
#else
   int fd;
#endif
   int in_nonblocking, want_nonblocking;
} TCPConnectionHandle;

typedef struct {
   volatile int refcnt;
   volatile int closed;
#if defined(_WIN32)
   SOCKET socket;
#else
   int fd;
#endif
} TCPServerHandle;

typedef struct AsyncThreadResult {
   int type;
   Value callback;
   Value data;
#if defined(_WIN32)
   SOCKET socket;
#else
   int fd;
#endif
   struct AsyncThreadResult *next;
} AsyncThreadResult;

typedef struct AsyncTimer {
   int immediate;
   uint32_t time;
   Value callback;
   Value data;
   struct AsyncTimer *next;
} AsyncTimer;

#ifdef _WIN32
typedef struct {
   DWORD transferred;
   ULONG_PTR key;
   void *overlapped;
} CompletedIO;
#endif

typedef struct {
   volatile int refcnt;
   pthread_mutex_t mutex;
   AsyncThreadResult *thread_results;
#if defined(_WIN32)
   HANDLE iocp;
   CompletedIO completed_ios[32];
   int num_events;
#else
   Poll *poll;
#endif
   int quit;
   Value quit_value;
   AsyncTimer *timers;

   pthread_mutex_t foreign_mutex;
   pthread_cond_t foreign_cond;
   IOEventNotifyFunc foreign_notify_func;
   void *foreign_notify_data;
   int foreign_processed;
} AsyncProcess;

typedef struct {
   Value callback;
   Value data;
   Value array;
   int off, len;
#if defined(_WIN32)
   char buf[1024];
   WSAOVERLAPPED overlapped;
#endif
} AsyncRead;

typedef struct {
   Value callback;
   Value data;
#if defined(_WIN32)
   char buf[1024];
   WSAOVERLAPPED overlapped;
#else
   int result;
#endif
} AsyncWrite;

typedef struct {
   AsyncProcess *proc;
   int type;
#if defined(_WIN32)
   SOCKET socket;
#else
   int fd;
   int last_active;
#endif
   int active;
   AsyncRead read;
   AsyncWrite write;
} AsyncHandle;

typedef struct {
   AsyncProcess *proc;
   int type;
#if defined(_WIN32)
   SOCKET socket;
   SOCKET accept_socket;
   char buf[(sizeof(struct sockaddr_in)+16)*2];
   OVERLAPPED overlapped;
#else
   int fd;
   int last_active;
#endif
   int active;
   Value callback;
   Value data;
} AsyncServerHandle;

typedef struct {
   volatile int refcnt;
   int flags;
#ifdef _WIN32
   HANDLE process;
   HANDLE in;
   HANDLE out;
   HANDLE err;
#else
   volatile pid_t pid;
   volatile int ret_value;
   int in_fd;
   int out_fd;
   int err_fd;
#endif
} ProcessHandle;

typedef int (*CompressFunc)(void *st);

#define NUM_HANDLE_TYPES 9
#define HANDLE_TYPE_ZCOMPRESS       (handles_offset+0)
#define HANDLE_TYPE_ZUNCOMPRESS     (handles_offset+1)
#define HANDLE_TYPE_GZIP_COMPRESS   (handles_offset+2)
#define HANDLE_TYPE_GZIP_UNCOMPRESS (handles_offset+3)
#define HANDLE_TYPE_FILE            (handles_offset+4)
#define HANDLE_TYPE_TCP_CONNECTION  (handles_offset+5)
#define HANDLE_TYPE_TCP_SERVER      (handles_offset+6)
#define HANDLE_TYPE_ASYNC           (handles_offset+7)
#define HANDLE_TYPE_PROCESS         (handles_offset+8)

static volatile int handles_offset;
static volatile int async_process_key;
static volatile int global_initialized;

typedef void (*ThreadFunc)(void *data);

typedef struct Thread {
   pthread_mutex_t mutex;
   pthread_cond_t cond;
   ThreadFunc func;
   void *data;
   struct Thread *next;
} Thread;

static pthread_mutex_t *threads_mutex;
static Thread *threads_pool;

#if !defined(_WIN32) && !defined(__HAIKU__)
extern const char **environ;
#endif


#ifdef _WIN32
static int pthread_mutex_init(pthread_mutex_t *mutex, void *attr)
{
   InitializeCriticalSection(mutex);
   return 0;
}

static int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
   DeleteCriticalSection(mutex);
   return 0;
}

static int pthread_mutex_lock(pthread_mutex_t *mutex)
{
   EnterCriticalSection(mutex);
   return 0;
}

static int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
   LeaveCriticalSection(mutex);
   return 0;
}

static int pthread_cond_init(pthread_cond_t *cond, void *attr)
{
   *cond = CreateEvent(NULL, FALSE, FALSE, NULL);
   if (!(*cond)) {
      return -1;
   }
   return 0;
}

static int pthread_cond_destroy(pthread_cond_t *cond)
{
   CloseHandle(*cond);
   *cond = NULL;
   return 0;
}

static int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
   LeaveCriticalSection(mutex);
   WaitForSingleObject(*cond, INFINITE);
   EnterCriticalSection(mutex);
   return 0;
}

static int pthread_cond_timedwait_relative(pthread_cond_t *cond, pthread_mutex_t *mutex, int64_t timeout)
{
   int ret = 0;
   LeaveCriticalSection(mutex);
   if (WaitForSingleObject(*cond, timeout/1000000) == WAIT_TIMEOUT) {
      ret = ETIMEDOUT;
   }
   EnterCriticalSection(mutex);
   return ret;
}

static int pthread_cond_signal(pthread_cond_t *cond)
{
   SetEvent(*cond);
   return 0;
}

#else

static int pthread_cond_timedwait_relative(pthread_cond_t *cond, pthread_mutex_t *mutex, int64_t timeout)
{
   struct timespec ts;
#if defined(__APPLE__)
   struct timeval tv;
   gettimeofday(&tv, NULL);
   ts.tv_sec = tv.tv_sec;
   ts.tv_nsec = tv.tv_usec * 1000;
#else
   clock_gettime(CLOCK_REALTIME, &ts);
#endif
   ts.tv_nsec += timeout % 1000000;
   ts.tv_sec += ts.tv_nsec / 1000000 + timeout / 1000000;
   ts.tv_nsec %= 1000000;
   return pthread_cond_timedwait(cond, mutex, &ts);
}
#endif


static int zcompress(ZCompress *st)
{
   #define PUT_BYTE(val)                                              \
   {                                                                  \
      if (st->dest == st->dest_end) {                                 \
         st->extra[st->extra_len++] = val;                            \
      }                                                               \
      else {                                                          \
         *st->dest++ = val;                                           \
      }                                                               \
   }

   #define PUT_BITS(val, nb)                                          \
   {                                                                  \
      st->bits |= (val) << st->num_bits;                              \
      st->num_bits += nb;                                             \
      while (st->num_bits >= 8) {                                     \
         PUT_BYTE(st->bits);                                          \
         st->bits >>= 8;                                              \
         st->num_bits -= 8;                                           \
      }                                                               \
   }

   #define PUT_SYM(val)                                               \
   {                                                                  \
      int v = val, b = syms[val], nb=8;                               \
      if (v >= 144 && v < 256) {                                      \
         b = (b << 1) | 1;                                            \
         nb = 9;                                                      \
      }                                                               \
      else if (v >= 256 && v < 280) {                                 \
         nb = 7;                                                      \
      }                                                               \
      PUT_BITS(b, nb);                                                \
   }

   #define PUT_LEN(val)                                               \
   {                                                                  \
      int i, vv = val, b=0, nb=0;                                     \
      if (vv == 258) {                                                \
         vv = 285;                                                    \
      }                                                               \
      else {                                                          \
         for (i=0; i<6; i++) {                                        \
            if (vv < len_base[i+1]) {                                 \
               vv -= len_base[i];                                     \
               b = vv & ((1 << i)-1);                                 \
               nb = i;                                                \
               vv = i > 0? 261+i*4 + (vv >> i) : 257+vv;              \
               break;                                                 \
            }                                                         \
         }                                                            \
      }                                                               \
      PUT_SYM(vv);                                                    \
      PUT_BITS(b, nb);                                                \
   }

   #define PUT_DIST(val)                                              \
   {                                                                  \
      int i, v = val, b=0, nb=0;                                      \
      for (i=0; i<14; i++) {                                          \
         if (v < dist_base[i+1]) {                                    \
            v -= dist_base[i];                                        \
            b = v & ((1 << i)-1);                                     \
            nb = i;                                                   \
            v = i > 0? 2+i*2 + (v >> i) : v;                          \
            break;                                                    \
         }                                                            \
      }                                                               \
      PUT_BITS(dists[v], 5);                                          \
      PUT_BITS(b, nb);                                                \
   }

   #define PUT_CIRCULAR(value)                                        \
   {                                                                  \
      int val = value;                                                \
      st->circular[st->circular_pos++] = val;                         \
      st->circular_pos &= 32767;                                      \
      if (st->circular_written < 32768) st->circular_written++;       \
   }

   #define SELECT_BUCKET(c1, c2, c3)                                  \
   {                                                                  \
      uint32_t idx = ((c1) << 16) | ((c2) << 8) | (c3);               \
      idx = (idx+0x7ed55d16) + (idx<<12);                             \
      idx = (idx^0xc761c23c) ^ (idx>>19);                             \
      idx = (idx+0x165667b1) + (idx<<5);                              \
      idx = (idx+0xd3a2646c) ^ (idx<<9);                              \
      idx = (idx+0xfd7046c5) + (idx<<3);                              \
      idx = (idx^0xb55a4f09) ^ (idx>>16);                             \
      bucket = st->hash + (idx & (num_buckets-1)) * num_slots;        \
   }

   #define GET_DIST(val)                                              \
   (                                                                  \
      st->circular_pos - (val) + ((val) >= st->circular_pos? 32768:0) \
   )

   #define CIRCULAR_MATCH(dist, c1, c2, c3)                           \
   (                                                                  \
      (c1)==st->circular[(st->circular_pos+32768-(dist)+0)&32767] &&  \
      (c2)==st->circular[(st->circular_pos+32768-(dist)+1)&32767] &&  \
      (c3)==st->circular[(st->circular_pos+32768-(dist)+2)&32767]     \
   )

   enum {
      STATE_INIT,
      STATE_MAIN,
      STATE_FLUSH,
      STATE_END,
      STATE_FINISH
   };

   const uint8_t syms[288] = {
      0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
      0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
      0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
      0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
      0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
      0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
      0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
      0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
      0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
      0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
      0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
      0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
      0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
      0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
      0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
      0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
      0x00, 0x40, 0x20, 0x60, 0x10, 0x50, 0x30, 0x70, 0x08, 0x48, 0x28, 0x68, 0x18, 0x58, 0x38, 0x78,
      0x04, 0x44, 0x24, 0x64, 0x14, 0x54, 0x34, 0x74, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3
   };
   const uint8_t dists[30] = {
      0x00, 0x10, 0x08, 0x18, 0x04, 0x14, 0x0c, 0x1c, 0x02, 0x12, 0x0a, 0x1a, 0x06, 0x16, 0x0e,
      0x1e, 0x01, 0x11, 0x09, 0x19, 0x05, 0x15, 0x0d, 0x1d, 0x03, 0x13, 0x0b, 0x1b, 0x07, 0x17
   };
   const uint16_t len_base[7] = { 3, 11, 19, 35, 67, 131, 258 };
   const uint16_t dist_base[15] = { 1, 5, 9, 17, 33, 65, 129, 257, 513, 1025, 2049, 4097, 8193, 16385, 32769 };

   int num_buckets = ZCOMP_NUM_BUCKETS;
   int num_slots = ZCOMP_NUM_SLOTS;

   int i, j, k, dist, len, best_len, best_dist=0, slot, worst_slot, worst_dist;
   unsigned short *bucket;
   unsigned char c;

   if (st->extra_len > 0) {
      len = st->dest_end - st->dest;
      if (len > st->extra_len) {
         len = st->extra_len;
      }
      memcpy(st->dest, st->extra, len);
      st->dest += len;
      memmove(st->extra, st->extra + len, st->extra_len - len);
      st->extra_len -= len;
      if (st->dest == st->dest_end) {
         return COMP_FLUSH;
      }
   }

   // max extra bytes:
   // init: 1 byte
   // output literal: 9 bits = 2 bytes
   // output repeat: (8+5)+(5+13) = 31 bits = 4 bytes
   // output trail: 2*9 = 18 bits = 3 bytes
   // flush: 7+3+7+4*8+3 = 52 bits = 7 bytes
   // ending: 7+7 = 14 bits = 2 bytes
   // ending (flushable): 7+3+7+7 = 24 bits = 3 bytes

again:
   switch (st->state) {
      case STATE_INIT:
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }
         PUT_BITS(st->flushable? 0 : 1, 1); // final block
         PUT_BITS(1, 2); // fixed Huffman codes
         st->state = STATE_MAIN;
         goto again;

      case STATE_MAIN:
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }
         if (!st->src_final && !st->src_flush && st->src_end - st->src < 258) {
            return COMP_MORE;
         }
         if (st->src_end - st->src < 3) {
            while (st->src < st->src_end) {
               c = *st->src++;
               PUT_SYM(c);
               PUT_CIRCULAR(c);
            }
            st->state = (st->src_flush && !st->src_final? STATE_FLUSH : STATE_END);
            st->src_flush = 0;
            goto again;
         }

         SELECT_BUCKET(st->src[0], st->src[1], st->src[2]);
         best_len = 0;
         slot = -1;
         worst_slot = 0;
         worst_dist = 0;
         for (i=0; i<num_slots; i++) {
            dist = GET_DIST(bucket[i]);
            if (dist <= st->circular_written && dist >= 3 && CIRCULAR_MATCH(dist, st->src[0], st->src[1], st->src[2])) {
               len = 3;
               for (j=3, k=dist-3; st->src+j < st->src_end && j<258; j++, k--) {
                  if (st->src[j] != (k > 0? st->circular[(st->circular_pos+32768-k) & 32767] : st->src[-k])) break;
                  len++;
               }
               if (len > best_len || (len == best_len && dist < best_dist)) {
                  best_len = len;
                  best_dist = dist;
               }
               if (dist > worst_dist) {
                  worst_slot = i;
                  worst_dist = dist;
               }
            }
            else if (slot < 0) {
               slot = i;
            }
         }

         if (slot < 0) {
            slot = worst_slot;
         }
         bucket[slot] = st->circular_pos;

         if (best_len >= 3) {
            PUT_LEN(best_len);
            PUT_DIST(best_dist);

            while (best_len > 0) {
               PUT_CIRCULAR(*st->src++);
               best_len--;
            }
         }
         else {
            c = *st->src++;
            PUT_SYM(c);
            PUT_CIRCULAR(c);
         }
         goto again;

      case STATE_FLUSH:
         if (!st->flushable) {
            goto error;
         }
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }
         PUT_SYM(256); // end of block
         PUT_BITS(0, 1); // not final block
         PUT_BITS(0, 2); // no compression
         if (st->num_bits > 0) {
            PUT_BITS(0, 8 - st->num_bits);
         }
         PUT_BYTE(0x00);
         PUT_BYTE(0x00);
         PUT_BYTE(0xFF);
         PUT_BYTE(0xFF);
         PUT_BITS(0, 1); // not final block
         PUT_BITS(1, 2); // fixed Huffman codes
         st->state = STATE_MAIN;
         return COMP_FLUSH;

      case STATE_END:
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }
         PUT_SYM(256); // end of block
         if (st->flushable) {
            PUT_BITS(1, 1); // final block
            PUT_BITS(1, 2); // fixed Huffman codes
            PUT_SYM(256); // end of block
         }
         if (st->num_bits > 0) {
            PUT_BITS(0, 8 - st->num_bits);
         }
         st->state = STATE_FINISH;
         return COMP_FLUSH;

      case STATE_FINISH:
         return COMP_DONE;
   }

error:
   return COMP_ERROR;

   #undef PUT_BYTE
   #undef PUT_BITS
   #undef PUT_SYM
   #undef PUT_LEN
   #undef PUT_DIST
   #undef PUT_CIRCULAR
   #undef SELECT_BUCKET
   #undef GET_DIST
   #undef CIRCULAR_MATCH
}


static int zcompress_memory(const unsigned char *src, int src_len, unsigned char **dest_out, int *dest_len_out)
{
   #define PUT_BYTE(val)                                              \
   {                                                                  \
      if (out_len == out_cap) {                                       \
         if (out_cap >= (1<<29)) goto error;                          \
         out_cap <<= 1;                                               \
         new_out = realloc(out, out_cap);                             \
         if (!new_out) goto error;                                    \
         out = new_out;                                               \
      }                                                               \
      out[out_len++] = val;                                           \
   }

   #define PUT_BITS(val, nb)                                          \
   {                                                                  \
      bits |= (val) << num_bits;                                      \
      num_bits += nb;                                                 \
      while (num_bits >= 8) {                                         \
         PUT_BYTE(bits);                                              \
         bits >>= 8;                                                  \
         num_bits -= 8;                                               \
      }                                                               \
   }

   #define PUT_SYM(val)                                               \
   {                                                                  \
      int v = val, b = syms[val], nb=8;                               \
      if (v >= 144 && v < 256) {                                      \
         b = (b << 1) | 1;                                            \
         nb = 9;                                                      \
      }                                                               \
      else if (v >= 256 && v < 280) {                                 \
         nb = 7;                                                      \
      }                                                               \
      PUT_BITS(b, nb);                                                \
   }

   #define PUT_LEN(val)                                               \
   {                                                                  \
      int i, vv = val, b=0, nb=0;                                     \
      if (vv == 258) {                                                \
         vv = 285;                                                    \
      }                                                               \
      else {                                                          \
         for (i=0; i<6; i++) {                                        \
            if (vv < len_base[i+1]) {                                 \
               vv -= len_base[i];                                     \
               b = vv & ((1 << i)-1);                                 \
               nb = i;                                                \
               vv = i > 0? 261+i*4 + (vv >> i) : 257+vv;              \
               break;                                                 \
            }                                                         \
         }                                                            \
      }                                                               \
      PUT_SYM(vv);                                                    \
      PUT_BITS(b, nb);                                                \
   }

   #define PUT_DIST(val)                                              \
   {                                                                  \
      int i, v = val, b=0, nb=0;                                      \
      for (i=0; i<14; i++) {                                          \
         if (v < dist_base[i+1]) {                                    \
            v -= dist_base[i];                                        \
            b = v & ((1 << i)-1);                                     \
            nb = i;                                                   \
            v = i > 0? 2+i*2 + (v >> i) : v;                          \
            break;                                                    \
         }                                                            \
      }                                                               \
      PUT_BITS(dists[v], 5);                                          \
      PUT_BITS(b, nb);                                                \
   }

   #define SELECT_BUCKET(c1, c2, c3)                                  \
   {                                                                  \
      uint32_t idx = ((c1) << 16) | ((c2) << 8) | (c3);               \
      idx = (idx+0x7ed55d16) + (idx<<12);                             \
      idx = (idx^0xc761c23c) ^ (idx>>19);                             \
      idx = (idx+0x165667b1) + (idx<<5);                              \
      idx = (idx+0xd3a2646c) ^ (idx<<9);                              \
      idx = (idx+0xfd7046c5) + (idx<<3);                              \
      idx = (idx^0xb55a4f09) ^ (idx>>16);                             \
      bucket = hash + (idx & (num_buckets-1)) * num_slots;            \
   }

   #define GET_INDEX(i, val)                                          \
   (                                                                  \
      ((i) & ~32767) + (val) - ((val) >= ((i) & 32767)? 32768 : 0)    \
   )

   const uint8_t syms[288] = {
      0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
      0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
      0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
      0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
      0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
      0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
      0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
      0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
      0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
      0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
      0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
      0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
      0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
      0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
      0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
      0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
      0x00, 0x40, 0x20, 0x60, 0x10, 0x50, 0x30, 0x70, 0x08, 0x48, 0x28, 0x68, 0x18, 0x58, 0x38, 0x78,
      0x04, 0x44, 0x24, 0x64, 0x14, 0x54, 0x34, 0x74, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3
   };
   const uint8_t dists[30] = {
      0x00, 0x10, 0x08, 0x18, 0x04, 0x14, 0x0c, 0x1c, 0x02, 0x12, 0x0a, 0x1a, 0x06, 0x16, 0x0e,
      0x1e, 0x01, 0x11, 0x09, 0x19, 0x05, 0x15, 0x0d, 0x1d, 0x03, 0x13, 0x0b, 0x1b, 0x07, 0x17
   };
   const uint16_t len_base[7] = { 3, 11, 19, 35, 67, 131, 258 };
   const uint16_t dist_base[15] = { 1, 5, 9, 17, 33, 65, 129, 257, 513, 1025, 2049, 4097, 8193, 16385, 32769 };

   int num_buckets = 4096; // 4096*8*2 = 64KB
   int num_slots = 8;

   unsigned char *out = NULL, *new_out;
   int out_len=0, out_cap=0;

   uint32_t bits = 0;
   int num_bits = 0;

   int i, j, k, idx, len, dist, best_len, best_dist=0, slot, worst_slot, worst_dist;
   unsigned short *hash = NULL, *bucket;

   out_cap = 4096;
   out = malloc(out_cap);
   if (!out) goto error;

   hash = calloc(num_buckets * num_slots, sizeof(unsigned short));
   if (!hash) goto error;

   PUT_BITS(1, 1); // final block
   PUT_BITS(1, 2); // fixed Huffman codes

   for (i=0; i<src_len-2; i++) {
      SELECT_BUCKET(src[i], src[i+1], src[i+2]);
      best_len = 0;
      slot = -1;
      worst_slot = 0;
      worst_dist = 0;
      for (j=0; j<num_slots; j++) {
         idx = GET_INDEX(i, bucket[j]);
         if (idx >= 0 && idx+2 < i && src[i+0] == src[idx+0] && src[i+1] == src[idx+1] && src[i+2] == src[idx+2]) {
            len = 3;
            for (k=3; k<(src_len-i) && k<258; k++) {
               if (src[i+k] != src[idx+k]) break;
               len++;
            }
            dist = i - idx;
            if (len > best_len || (len == best_len && dist < best_dist)) {
               best_len = len;
               best_dist = dist;
            }
            if (dist > worst_dist) {
               worst_slot = j;
               worst_dist = dist;
            }
         }
         else if (slot < 0) {
            slot = j;
         }
      }

      if (slot < 0) {
         slot = worst_slot;
      }
      bucket[slot] = i & 32767;

      if (best_len >= 3) {
         PUT_LEN(best_len);
         PUT_DIST(best_dist);
         i += best_len-1;
      }
      else {
         PUT_SYM(src[i]);
      }
   }
   for (; i<src_len; i++) {
      PUT_SYM(src[i]);
   }
   PUT_SYM(256); // end of block

   // flush last byte:
   if (num_bits > 0) {
      PUT_BITS(0, 8);
   }
   
   *dest_out = out;
   *dest_len_out = out_len;
   free(hash);
   return 1;

error:
   free(out);
   free(hash);
   return 0;

   #undef PUT_BYTE
   #undef PUT_BITS
   #undef PUT_SYM
   #undef PUT_LEN
   #undef PUT_DIST
   #undef SELECT_BUCKET
   #undef GET_INDEX
}


/*
The Canonical Huffman decompression works as follow:

1. the code length is obtained for each symbol
2. the number of symbols for each code length is computed (ignoring zero code lengths)
3. sorted table of symbols is created, it is sorted by code length
4. when decoding the different code lengths are iterated over, with these steps:
   a) starting code word is computed for given code length
   b) code word is matched when current code word matches the interval of values for current code length
   c) the index to the sorted table is simply incremented by the count of symbols for given code length
*/

static int zuncompress(ZUncompress *st)
{
   #define GET_BITS(dest, nb)                                         \
   {                                                                  \
      while (st->num_bits < nb) {                                     \
         if (st->src == st->src_end) goto retry;                      \
         st->bits |= (*st->src++) << st->num_bits;                    \
         st->num_bits += 8;                                           \
      }                                                               \
      dest = st->bits & ((1 << (nb))-1);                              \
      st->bits >>= nb;                                                \
      st->num_bits -= nb;                                             \
   }

   #define HUFF_BUILD(lengths, num_symbols, max_len, symbols, counts) \
   {                                                                  \
      int i, j, cnt=0;                                                \
                                                                      \
      for (i=1; i<max_len; i++) {                                     \
         for (j=0; j<(num_symbols); j++) {                            \
            if ((lengths)[j] == i) {                                  \
               symbols[cnt++] = j;                                    \
            }                                                         \
         }                                                            \
      }                                                               \
      if (cnt == 0) goto error;                                       \
                                                                      \
      memset(counts, 0, sizeof(counts));                              \
      for (i=0; i<(num_symbols); i++) {                               \
         counts[(lengths)[i]]++;                                      \
      }                                                               \
      counts[0] = 0;                                                  \
   }

   #define HUFF_DECODE(sym, symbols, counts, max_len)                 \
   {                                                                  \
      int bit, match_bits=0, idx=0, code=0, i;                        \
      sym = -1;                                                       \
      for (i=1; i<max_len; i++) {                                     \
         GET_BITS(bit, 1);                                            \
         match_bits = (match_bits << 1) | bit;                        \
         code = (code + counts[i-1]) << 1;                            \
         if (match_bits >= code && match_bits < code + counts[i]) {   \
            sym = symbols[idx + (match_bits - code)];                 \
            break;                                                    \
         }                                                            \
         idx += counts[i];                                            \
      }                                                               \
      if (sym == -1) goto error;                                      \
   }

   #define PUT_BYTE(val)                                              \
   {                                                                  \
      int value = val;                                                \
      *st->dest++ = value;                                            \
      st->circular[st->circular_pos++] = value;                       \
      st->circular_pos &= 32767;                                      \
      if (st->circular_written < 32768) st->circular_written++;       \
   }

   enum {
      STATE_READ_HEADER,
      STATE_UNCOMPRESSED,
      STATE_COMPRESSED,
      STATE_REPEAT,
      STATE_FINISH
   };

   static const char prelength_reorder[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
   static const uint16_t len_base[29] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 };
   static const uint8_t  len_bits[29] = { 0, 0, 0, 0, 0, 0, 0,  0,  1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,   4,   5,   5,   5,   5,   0 };
   static const uint16_t dist_base[30] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 };
   static const uint8_t  dist_bits[30] = { 0, 0, 0, 0, 1, 1, 2,  2,  3,  3,  4,  4,  5,  5,   6,   6,   7,   7,   8,   8,    9,    9,   10,   10,   11,   11,   12,    12,    13,    13 };
   
   int type;
   int len, nlen;
   int hlit, hdist, hclen, pos, limit;

   uint8_t prelengths[19], precounts[8], presymbols[19];
   uint8_t lengths[320];

   int i, sym;

   const unsigned char *start_src;
   uint32_t start_bits;
   int start_num_bits;
   int start_final_block;

   // maximum input sizes:
   // block header: 1 byte
   // no compression: 1+4 bytes
   // dynamic: 5+5+4+19*3+320*(7+7)=4551 bits = 1+569 bytes
   // limit=257+31+1+31=320
   // decode: 15+5+15+13=48 bits = 6 bytes

again:
   start_src = st->src;
   start_bits = st->bits;
   start_num_bits = st->num_bits;
   start_final_block = st->final_block;

   switch (st->state) {
      case STATE_READ_HEADER:
         if (st->final_block) {
            st->state = STATE_FINISH;
            return COMP_FLUSH;
         }
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }
         GET_BITS(st->final_block, 1);
         GET_BITS(type, 2);
         if (type == 3) goto error;

         if (type == 0) {
            // no compression:

            st->bits = 0;
            st->num_bits = 0;

            if (st->src_end - st->src < 4) goto retry;
            len = st->src[0] | (st->src[1] << 8);
            nlen = st->src[2] | (st->src[3] << 8);
            if (len != ((~nlen) & 0xFFFF)) goto error;
            st->src += 4;

            st->state = STATE_UNCOMPRESSED;
            st->remaining = len;
            goto again;
         }

         if (type == 2) {
            // dynamic tree:

            GET_BITS(hlit, 5);
            GET_BITS(hdist, 5);
            GET_BITS(hclen, 4);

            limit = 257 + hlit + 1 + hdist;

            for (i=0; i<4+hclen; i++) {
               GET_BITS(prelengths[(int)prelength_reorder[i]], 3);
            }
            for (; i<19; i++) {
               prelengths[(int)prelength_reorder[i]] = 0;
            }
            HUFF_BUILD(prelengths, 19, 8, presymbols, precounts);

            pos = 0;
            while (pos < limit) {
               HUFF_DECODE(sym, presymbols, precounts, 8);
               if (sym < 16) {
                  lengths[pos++] = sym;
               }
               else if (sym == 16) {
                  GET_BITS(len, 2);
                  len += 3;
                  if (pos == 0 || pos + len > limit) goto error;
                  for (i=0; i<len; i++) {
                     lengths[pos+i] = lengths[pos-1];
                  }
                  pos += len;
               }
               else if (sym == 17) {
                  GET_BITS(len, 3);
                  len += 3;
                  if (pos + len > limit) goto error;
                  for (i=0; i<len; i++) {
                     lengths[pos++] = 0;
                  }
               }
               else if (sym == 18) {
                  GET_BITS(len, 7);
                  len += 11;
                  if (pos + len > limit) goto error;
                  for (i=0; i<len; i++) {
                     lengths[pos++] = 0;
                  }
               }
               else goto error;
            }

            if (lengths[256] == 0) goto error;
         }
         else {
            // static tree:

            for (i=0; i<144; i++) {
               lengths[i] = 8;
            }
            for (i=144; i<256; i++) {
               lengths[i] = 9;
            }
            for (i=256; i<280; i++) {
               lengths[i] = 7;
            }
            for (i=280; i<288; i++) {
               lengths[i] = 8;
            }
            for (i=288; i<320; i++) {
               lengths[i] = 5;
            }
            hlit = 31;
            hdist = 31;
         }

         HUFF_BUILD(lengths, 257+hlit, 16, st->lit_symbols, st->lit_counts);
         HUFF_BUILD(lengths+(257+hlit), 1+hdist, 16, st->dist_symbols, st->dist_counts);

         st->state = STATE_COMPRESSED;
         goto again;

      case STATE_UNCOMPRESSED:
         if (st->src == st->src_end) {
            return COMP_MORE;
         }
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }

         len = st->remaining;
         if (len > st->dest_end - st->dest) {
            len = st->dest_end - st->dest;
         }

         for (i=0; i<len; i++) {
            PUT_BYTE(*st->src++);
         }
         st->remaining -= len;
         
         if (st->remaining == 0) {
            st->state = STATE_READ_HEADER;
         }
         goto again;

      case STATE_COMPRESSED:
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }

         HUFF_DECODE(sym, st->lit_symbols, st->lit_counts, 16);
         if (sym < 256) {
            PUT_BYTE(sym);
            goto again;
         }
         if (sym == 256) {
            st->state = STATE_READ_HEADER;
            goto again;
         }
         if (sym > 285) {
            goto error;
         }

         GET_BITS(len, len_bits[sym-257]);
         len += len_base[sym-257];

         HUFF_DECODE(sym, st->dist_symbols, st->dist_counts, 16);
         if (sym > 29) goto error;

         GET_BITS(st->dist, dist_bits[sym]);
         st->dist += dist_base[sym];

         if (st->dist > st->circular_written) goto error;

         st->state = STATE_REPEAT;
         st->remaining = len;
         goto again;

      case STATE_REPEAT:
         if (st->dest == st->dest_end) {
            return COMP_FLUSH;
         }

         len = st->remaining;
         if (len > st->dest_end - st->dest) {
            len = st->dest_end - st->dest;
         }

         for (i=0; i<len; i++) {
            PUT_BYTE(st->circular[(st->circular_pos + 32768 - st->dist) & 32767]);
         }
         st->remaining -= len;

         if (st->remaining == 0) {
            st->state = STATE_COMPRESSED;
         }
         goto again;

      case STATE_FINISH:
         return COMP_DONE;
   }

error:
   return COMP_ERROR;

retry:
   st->src = start_src;
   st->bits = start_bits;
   st->num_bits = start_num_bits;
   st->final_block = start_final_block;
   return COMP_MORE;

   #undef GET_BITS
   #undef HUFF_BUILD
   #undef HUFF_DECODE
   #undef PUT_BYTE
}


static int zuncompress_memory(const unsigned char *src, int src_len, unsigned char **dest_out, int *dest_len_out)
{
   #define GET_BITS(dest, nb)                                         \
   {                                                                  \
      while (num_bits < nb) {                                         \
         if (src == end) goto error;                                  \
         bits |= (*src++) << num_bits;                                \
         num_bits += 8;                                               \
      }                                                               \
      dest = bits & ((1 << (nb))-1);                                  \
      bits >>= nb;                                                    \
      num_bits -= nb;                                                 \
   }

   #define HUFF_BUILD(lengths, num_symbols, max_len, symbols, counts) \
   {                                                                  \
      int i, j, cnt=0;                                                \
                                                                      \
      for (i=1; i<max_len; i++) {                                     \
         for (j=0; j<(num_symbols); j++) {                            \
            if ((lengths)[j] == i) {                                  \
               symbols[cnt++] = j;                                    \
            }                                                         \
         }                                                            \
      }                                                               \
      if (cnt == 0) goto error;                                       \
                                                                      \
      memset(counts, 0, sizeof(counts));                              \
      for (i=0; i<(num_symbols); i++) {                               \
         counts[(lengths)[i]]++;                                      \
      }                                                               \
      counts[0] = 0;                                                  \
   }

   #define HUFF_DECODE(sym, symbols, counts, max_len)                 \
   {                                                                  \
      int bit, match_bits=0, idx=0, code=0, i;                        \
      sym = -1;                                                       \
      for (i=1; i<max_len; i++) {                                     \
         GET_BITS(bit, 1);                                            \
         match_bits = (match_bits << 1) | bit;                        \
         code = (code + counts[i-1]) << 1;                            \
         if (match_bits >= code && match_bits < code + counts[i]) {   \
            sym = symbols[idx + (match_bits - code)];                 \
            break;                                                    \
         }                                                            \
         idx += counts[i];                                            \
      }                                                               \
      if (sym == -1) goto error;                                      \
   }

   #define PUT_BYTE(value)                                            \
   {                                                                  \
      int val = value;                                                \
      if (out_len == out_cap) {                                       \
         if (out_cap >= (1<<29)) goto error;                          \
         out_cap <<= 1;                                               \
         new_out = realloc(out, out_cap);                             \
         if (!new_out) goto error;                                    \
         out = new_out;                                               \
      }                                                               \
      out[out_len++] = val;                                           \
   }

   static const uint8_t  prelength_reorder[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
   static const uint16_t len_base[29] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 };
   static const uint8_t  len_bits[29] = { 0, 0, 0, 0, 0, 0, 0,  0,  1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,   4,   5,   5,   5,   5,   0 };
   static const uint16_t dist_base[30] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 };
   static const uint8_t  dist_bits[30] = { 0, 0, 0, 0, 1, 1, 2,  2,  3,  3,  4,  4,  5,  5,   6,   6,   7,   7,   8,   8,    9,    9,   10,   10,   11,   11,   12,    12,    13,    13 };
   
   const unsigned char *end = src + src_len;
   uint32_t bits = 0;
   int num_bits = 0;

   unsigned char *out = NULL, *new_out;
   int out_len=0, out_cap=0;
   
   int final, type;
   int len, nlen;
   int hlit, hdist, hclen, pos, limit;
   
   uint8_t prelengths[19], precounts[8], presymbols[19];
   uint8_t lengths[320];
   uint16_t lit_symbols[288], lit_counts[16];
   uint8_t dist_symbols[32], dist_counts[16];

   int i, sym, dist;

   out_cap = 4096;
   out = malloc(out_cap);
   if (!out) goto error;

   for (;;) {
      GET_BITS(final, 1);
      GET_BITS(type, 2);
      if (type == 3) goto error;

      if (type == 0) {
         // no compression:

         bits = 0;
         num_bits = 0;

         if (end - src < 4) goto error;
         len = src[0] | (src[1] << 8);
         nlen = src[2] | (src[3] << 8);
         if (len != ((~nlen) & 0xFFFF)) goto error;
         src += 4;
         if (end - src < len) goto error;
         for (i=0; i<len; i++) {
            PUT_BYTE(*src++);
         }
         if (final) break;
         continue;
      }

      if (type == 2) {
         // dynamic tree:

         GET_BITS(hlit, 5);
         GET_BITS(hdist, 5);
         GET_BITS(hclen, 4);

         limit = 257 + hlit + 1 + hdist;

         for (i=0; i<4+hclen; i++) {
            GET_BITS(prelengths[prelength_reorder[i]], 3);
         }
         for (; i<19; i++) {
            prelengths[prelength_reorder[i]] = 0;
         }
         HUFF_BUILD(prelengths, 19, 8, presymbols, precounts);

         pos = 0;
         while (pos < limit) {
            HUFF_DECODE(sym, presymbols, precounts, 8);
            if (sym < 16) {
               lengths[pos++] = sym;
            }
            else if (sym == 16) {
               GET_BITS(len, 2);
               len += 3;
               if (pos == 0 || pos + len > limit) goto error;
               for (i=0; i<len; i++) {
                  lengths[pos+i] = lengths[pos-1];
               }
               pos += len;
            }
            else if (sym == 17) {
               GET_BITS(len, 3);
               len += 3;
               if (pos + len > limit) goto error;
               for (i=0; i<len; i++) {
                  lengths[pos++] = 0;
               }
            }
            else if (sym == 18) {
               GET_BITS(len, 7);
               len += 11;
               if (pos + len > limit) goto error;
               for (i=0; i<len; i++) {
                  lengths[pos++] = 0;
               }
            }
            else goto error;
         }

         if (lengths[256] == 0) goto error;
      }
      else {
         // static tree:

         for (i=0; i<144; i++) {
            lengths[i] = 8;
         }
         for (i=144; i<256; i++) {
            lengths[i] = 9;
         }
         for (i=256; i<280; i++) {
            lengths[i] = 7;
         }
         for (i=280; i<288; i++) {
            lengths[i] = 8;
         }
         for (i=288; i<320; i++) {
            lengths[i] = 5;
         }
         hlit = 31;
         hdist = 31;
      }

      HUFF_BUILD(lengths, 257+hlit, 16, lit_symbols, lit_counts);
      HUFF_BUILD(lengths+(257+hlit), 1+hdist, 16, dist_symbols, dist_counts);

      for (;;) {
         HUFF_DECODE(sym, lit_symbols, lit_counts, 16);
         if (sym < 256) {
            PUT_BYTE(sym);
            continue;
         }
         if (sym == 256) {
            break;
         }
         if (sym > 285) {
            goto error;
         }

         GET_BITS(len, len_bits[sym-257]);
         len += len_base[sym-257];

         HUFF_DECODE(sym, dist_symbols, dist_counts, 16);
         if (sym > 29) goto error;

         GET_BITS(dist, dist_bits[sym]);
         dist += dist_base[sym];

         if (out_len - dist < 0) goto error;

         for (i=0; i<len; i++) {
            PUT_BYTE(out[out_len-dist]);
         }
      }

      if (final) break;
   }
   
   *dest_out = out;
   *dest_len_out = out_len;
   return 1;

error:
   free(out);
   return 0;

   #undef GET_BITS
   #undef HUFF_BUILD
   #undef HUFF_DECODE
   #undef PUT_BYTE
}


static uint32_t calc_crc32(uint32_t crc, const unsigned char *buf, int len)
{
   static uint32_t table[256] = {
      0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
      0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
      0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
      0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
      0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
      0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
      0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
      0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
      0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
      0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
      0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
      0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
      0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
      0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
      0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
      0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
      0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
      0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
      0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
      0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
      0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
      0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
      0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
      0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
      0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
      0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
      0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
      0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
      0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
      0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
      0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
      0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
   };
   int i;
   
   for (i=0; i<len; i++) {
      crc = table[(crc ^ buf[i]) & 0xFF] ^ (crc >> 8);
   }
   return crc;
}


static int gzip_compress(GZipCompress *st)
{
   #define PUT_BYTE(val)                                              \
   {                                                                  \
      if (st->z.dest == st->z.dest_end) {                             \
         st->extra[st->extra_len++] = val;                            \
      }                                                               \
      else {                                                          \
         *st->z.dest++ = val;                                         \
      }                                                               \
   }

   enum {
      STATE_HEADER,
      STATE_MAIN,
      STATE_FOOTER,
      STATE_FINISH
   };

   const unsigned char *prev_src;
   int len, ret;

   if (st->extra_len > 0) {
      len = st->z.dest_end - st->z.dest;
      if (len > st->extra_len) {
         len = st->extra_len;
      }
      memcpy(st->z.dest, st->extra, len);
      st->z.dest += len;
      memmove(st->extra, st->extra + len, st->extra_len - len);
      st->extra_len -= len;
      if (st->z.dest == st->z.dest_end) {
         return COMP_FLUSH;
      }
   }

again:
   switch (st->state) {
      case STATE_HEADER:
         if (st->z.dest == st->z.dest_end) {
            return COMP_FLUSH;
         }
         PUT_BYTE(0x1f);
         PUT_BYTE(0x8b);
         PUT_BYTE(8); // deflate
         PUT_BYTE(0); // flags
         PUT_BYTE(0); // mtime
         PUT_BYTE(0);
         PUT_BYTE(0);
         PUT_BYTE(0);
         PUT_BYTE(0); // medium=0 fastest=4
         PUT_BYTE(3); // unix
         st->state = STATE_MAIN;
         st->crc = 0xFFFFFFFF;
         if (st->z.dest == st->z.dest_end) {
            return COMP_FLUSH;
         }
         goto again;

      case STATE_MAIN:
         prev_src = st->z.src;
         ret = zcompress(&st->z);
         st->crc = calc_crc32(st->crc, prev_src, st->z.src - prev_src);
         st->in_size += st->z.src - prev_src;
         if (ret == COMP_DONE) {
            st->state = STATE_FOOTER;
            goto again;
         }
         return ret;

      case STATE_FOOTER:
         if (st->z.dest == st->z.dest_end) {
            return COMP_FLUSH;
         }
         st->crc ^= 0xFFFFFFFF;
         PUT_BYTE(st->crc);
         PUT_BYTE(st->crc >> 8);
         PUT_BYTE(st->crc >> 16);
         PUT_BYTE(st->crc >> 24);
         PUT_BYTE(st->in_size);
         PUT_BYTE(st->in_size >> 8);
         PUT_BYTE(st->in_size >> 16);
         PUT_BYTE(st->in_size >> 24);
         st->state = STATE_FINISH;
         return COMP_FLUSH;

      case STATE_FINISH:
         return COMP_DONE;
   }

   return COMP_ERROR;

   #undef PUT_BYTE
}


static int gzip_compress_memory(const unsigned char *src, int src_len, unsigned char **dest_out, int *dest_len_out)
{
   unsigned char *dest = NULL, *comp = NULL, *p;
   uint32_t crc;
   int dest_len, comp_len, retval=0;

   if (!zcompress_memory(src, src_len, &comp, &comp_len)) goto error;

   dest_len = 10 + comp_len + 8;
   dest = malloc(dest_len);
   if (!dest) goto error;

   dest[0] = 0x1f;
   dest[1] = 0x8b;
   dest[2] = 8; // deflate
   dest[3] = 0; // flags
   dest[4] = 0; // mtime
   dest[5] = 0;
   dest[6] = 0;
   dest[7] = 0;
   dest[8] = 0; // medium=0 fastest=4
   dest[9] = 3; // unix
   memcpy(dest+10, comp, comp_len);

   p = dest+10+comp_len;
   crc = calc_crc32(0xFFFFFFFF, src, src_len) ^ 0xFFFFFFFF;
   *p++ = crc;
   *p++ = crc >> 8;
   *p++ = crc >> 16;
   *p++ = crc >> 24;
   *p++ = src_len;
   *p++ = src_len >> 8;
   *p++ = src_len >> 16;
   *p++ = src_len >> 24;

   *dest_out = dest;
   *dest_len_out = dest_len;
   dest = NULL;
   retval = 1;
   
error:
   free(comp);
   free(dest);
   return retval;
}


static int gzip_uncompress(GZipUncompress *st)
{
   enum {
      STATE_HEADER,
      STATE_FEXTRA,
      STATE_FEXTRA_CONTENT,
      STATE_FNAME,
      STATE_FCOMMENT,
      STATE_FHCRC,
      STATE_UNCOMPRESS,
      STATE_FOOTER,
      STATE_FINISH
   };

   int len, ret;
   uint32_t crc32, isize;
   const unsigned char *prev_dest, *start_src;

again:
   start_src = st->z.src;

   switch (st->state) {
      case STATE_HEADER:
         if (st->z.src_end - st->z.src < 10) {
            goto retry;
         }

         if (st->z.src[0] != 0x1f || st->z.src[1] != 0x8b || st->z.src[2] != 8) {
            goto error;
         }
         st->flags = st->z.src[3];
         st->z.src += 10;

         if (st->flags & GZIP_FEXTRA) {
            st->state = STATE_FEXTRA;
         }
         else if (st->flags & GZIP_FNAME) {
            st->state = STATE_FNAME;
         }
         else if (st->flags & GZIP_FCOMMENT) {
            st->state = STATE_FCOMMENT;
         }
         else if (st->flags & GZIP_FHCRC) {
            st->state = STATE_FHCRC;
         }
         else {
            st->state = STATE_UNCOMPRESS;
         }
         st->crc = 0xFFFFFFFF;
         goto again;

      case STATE_FEXTRA:
         if (st->z.src_end - st->z.src < 2) {
            goto retry;
         }
         st->state = STATE_FEXTRA_CONTENT;
         st->remaining = st->z.src[0] | (st->z.src[1] << 8);
         st->z.src += 2;
         goto again;

      case STATE_FEXTRA_CONTENT:
         if (st->z.src == st->z.src_end) {
            return COMP_MORE;
         }

         len = st->remaining;
         if (len > st->z.dest_end - st->z.dest) {
            len = st->z.dest_end - st->z.dest;
         }

         st->z.src += len;
         st->remaining -= len;
         
         if (st->remaining == 0) {
            if (st->flags & GZIP_FNAME) {
               st->state = STATE_FNAME;
            }
            else if (st->flags & GZIP_FCOMMENT) {
               st->state = STATE_FCOMMENT;
            }
            else if (st->flags & GZIP_FHCRC) {
               st->state = STATE_FHCRC;
            }
            else {
               st->state = STATE_UNCOMPRESS;
            }
         }
         goto again;

      case STATE_FNAME:
      case STATE_FCOMMENT:
         if (st->z.src == st->z.src_end) {
            return COMP_MORE;
         }
         while (st->z.src < st->z.src_end) {
            if (*st->z.src++ == 0) {
               if (st->state == STATE_FNAME && (st->flags & GZIP_FCOMMENT)) {
                  st->state = STATE_FCOMMENT;
               }
               else if (st->flags & GZIP_FHCRC) {
                  st->state = STATE_FHCRC;
               }
               else {
                  st->state = STATE_UNCOMPRESS;
               }
               goto again;
            }
         }
         goto again;

      case STATE_FHCRC:
         if (st->z.src_end - st->z.src < 2) {
            goto retry;
         }
         st->state = STATE_UNCOMPRESS;
         st->z.src += 2;
         goto again;

      case STATE_UNCOMPRESS:
         prev_dest = st->z.dest;
         ret = zuncompress(&st->z);
         st->crc = calc_crc32(st->crc, prev_dest, st->z.dest - prev_dest);
         st->out_size += st->z.dest - prev_dest;
         if (ret == COMP_DONE) {
            st->state = STATE_FOOTER;
            goto again;
         }
         return ret;

      case STATE_FOOTER:
         if (st->z.src_end - st->z.src < 8) {
            goto retry;
         }
         
         crc32 = st->z.src[0] | (st->z.src[1] << 8) | (st->z.src[2] << 16) | (st->z.src[3] << 24);
         isize = st->z.src[4] | (st->z.src[5] << 8) | (st->z.src[6] << 16) | (st->z.src[7] << 24);

         if (isize != st->out_size || crc32 != (st->crc ^ 0xFFFFFFFF)) {
            goto error;
         }

         st->z.src += 8;
         st->state = STATE_FINISH;
         return COMP_FLUSH;

      case STATE_FINISH:
         return COMP_DONE;
   }

error:
   return COMP_ERROR;

retry:
   st->z.src = start_src;
   return COMP_MORE;
}


static int gzip_uncompress_memory(const unsigned char *src, int src_len, unsigned char **dest_out, int *dest_len_out)
{
   unsigned char *dest = NULL;
   int dest_len;
   int flags;
   int extra_len;
   uint32_t crc32, isize;
   
   if (src_len < 10) {
      goto error;
   }
   
   if (src[0] != 0x1f || src[1] != 0x8b || src[2] != 8) {
      goto error;
   }

   flags = src[3];

   src += 10;
   src_len -= 10;

   if (flags & GZIP_FEXTRA) {
      if (src_len < 2) {
         goto error;
      }
      extra_len = src[0] | (src[1] << 8);
      if (src_len < 2+extra_len) {
         goto error;
      }

      src += 2+extra_len;
      src_len -= 2+extra_len;
   }

   if (flags & GZIP_FNAME) {
      for (;;) {
         if (src_len == 0) {
            goto error;
         }
         src_len--;
         if (*src++ == 0) {
            break;
         }
      }
   }

   if (flags & GZIP_FCOMMENT) {
      for (;;) {
         if (src_len == 0) {
            goto error;
         }
         src_len--;
         if (*src++ == 0) {
            break;
         }
      }
   }

   if (flags & GZIP_FHCRC) {
      if (src_len < 2) {
         goto error;
      }
      src += 2;
      src_len -= 2;
   }

   if (src_len < 8) {
      goto error;
   }
   
   if (!zuncompress_memory(src, src_len-8, &dest, &dest_len)) {
      goto error;
   }

   src += src_len-8;
   src_len = 8;
   
   crc32 = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
   isize = src[4] | (src[5] << 8) | (src[6] << 16) | (src[7] << 24);

   if (isize != dest_len || crc32 != (calc_crc32(0xFFFFFFFF, dest, dest_len) ^ 0xFFFFFFFF)) {
      goto error;
   }

   *dest_out = dest;
   *dest_len_out = dest_len;
   return 1;

error:
   free(dest);
   return 0;
}


static Value native_zcompress_memory(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int flags = (intptr_t)data;
   Value ret;
   int err, ok, off, len, out_len;
   unsigned char *in, *out;

   if (num_params == 3) {
      off = fixscript_get_int(params[1]);
      len = fixscript_get_int(params[2]);
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, params[0], &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   err = fixscript_lock_array(heap, params[0], off, len, (void **)&in, 1, 1);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (flags & ZC_COMPRESS) {
      if (flags & ZC_GZIP) {
         ok = gzip_compress_memory(in, len, &out, &out_len);
      }
      else {
         ok = zcompress_memory(in, len, &out, &out_len);
      }
   }
   else {
      if (flags & ZC_GZIP) {
         ok = gzip_uncompress_memory(in, len, &out, &out_len);
      }
      else {
         ok = zuncompress_memory(in, len, &out, &out_len);
      }
   }
   fixscript_unlock_array(heap, params[0], off, len, (void **)&in, 1, 0);

   if (!ok) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   ret = fixscript_create_or_get_shared_array(heap, -1, out, out_len, 1, free, out, NULL);
   if (!ret.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   return ret;
}


static void free_compress_handle(void *ptr)
{
   CompressHandle *ch = ptr;
   
   fixscript_adjust_heap_size(ch->heap, -ch->size);
   free(ch->state);
   free(ch);
}


static Value native_zcompress_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int flags = (intptr_t)data;
   CompressHandle *ch;
   Value ret;
   int size, type;
   
   if (flags & ZC_COMPRESS) {
      if (flags & ZC_GZIP) {
         type = HANDLE_TYPE_GZIP_COMPRESS;
         size = sizeof(GZipCompress);
      }
      else {
         type = HANDLE_TYPE_ZCOMPRESS;
         size = sizeof(ZCompress);
      }
   }
   else {
      if (flags & ZC_GZIP) {
         type = HANDLE_TYPE_GZIP_UNCOMPRESS;
         size = sizeof(GZipUncompress);
      }
      else {
         type = HANDLE_TYPE_ZUNCOMPRESS;
         size = sizeof(ZUncompress);
      }
   }

   ch = calloc(1, sizeof(CompressHandle));
   if (!ch) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   ch->state = calloc(1, size);
   if (!ch->state) {
      free(ch);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   
   if (flags & ZC_COMPRESS) {
      ((ZCompress *)ch->state)->flushable = params[0].value;
   }

   ch->heap = heap;
   ch->size = sizeof(CompressHandle) + size;
   fixscript_adjust_heap_size(ch->heap, ch->size);

   ret = fixscript_create_handle(heap, type, ch, free_compress_handle);
   if (!ret.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   return ret;
}


static Value native_zcompress_process(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   CompressHandle *ch;
   CompressFunc func;
   ZCommon *common;
   Value src_val, dest_val;
   int err, ret, type;
   int src_off, src_len, dest_off, dest_len, final;
   unsigned char *src, *dest;

   ch = fixscript_get_handle(heap, params[0], -1, &type);
   if (!ch) {
      *error = fixscript_create_error_string(heap, "invalid handle");
      return fixscript_int(0);
   }

   if (type == HANDLE_TYPE_ZCOMPRESS) {
      func = (CompressFunc)zcompress;
   }
   else if (type == HANDLE_TYPE_ZUNCOMPRESS) {
      func = (CompressFunc)zuncompress;
   }
   else if (type == HANDLE_TYPE_GZIP_COMPRESS) {
      func = (CompressFunc)gzip_compress;
   }
   else if (type == HANDLE_TYPE_GZIP_UNCOMPRESS) {
      func = (CompressFunc)gzip_uncompress;
   }
   else {
      *error = fixscript_create_error_string(heap, "invalid handle");
      return fixscript_int(0);
   }

   src_val = params[1];
   src_off = params[2].value;
   src_len = params[3].value;
   dest_val = params[4];
   dest_off = params[5].value;
   dest_len = params[6].value;
   final = params[7].value;

   err = fixscript_lock_array(heap, src_val, src_off, src_len, (void **)&src, 1, 1);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_lock_array(heap, dest_val, dest_off, dest_len, (void **)&dest, 1, 0);
   if (err) {
      fixscript_unlock_array(heap, src_val, src_off, src_len, (void **)&src, 1, 0);
      return fixscript_error(heap, error, err);
   }

   common = ch->state;
   common->src = src;
   common->src_end = src + src_len;
   common->dest = dest;
   common->dest_end = dest + dest_len;
   if (type == HANDLE_TYPE_ZCOMPRESS || type == HANDLE_TYPE_GZIP_COMPRESS) {
      ((ZCompress *)common)->src_final = (final == 1);
      ((ZCompress *)common)->src_flush = (final == 2);
   }

   ret = func(ch->state);

   ch->read = common->src - src;
   ch->written = common->dest - dest;

   fixscript_unlock_array(heap, src_val, src_off, src_len, (void **)&src, 1, 0);
   fixscript_unlock_array(heap, dest_val, dest_off, dest_len, (void **)&dest, 1, 1);

   if (ret < 0) {
      if (type == HANDLE_TYPE_ZCOMPRESS || type == HANDLE_TYPE_GZIP_COMPRESS) {
         *error = fixscript_create_error_string(heap, "compression error");
      }
      else {
         *error = fixscript_create_error_string(heap, "decompression error");
      }
      return fixscript_int(0);
   }
   return fixscript_int(ret);
}


static Value native_zcompress_get_info(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   CompressHandle *ch;
   int type;

   ch = fixscript_get_handle(heap, params[0], -1, &type);
   if (!ch) {
      *error = fixscript_create_error_string(heap, "invalid handle");
      return fixscript_int(0);
   }

   if (type != HANDLE_TYPE_ZCOMPRESS && type != HANDLE_TYPE_ZUNCOMPRESS && type != HANDLE_TYPE_GZIP_COMPRESS && type != HANDLE_TYPE_GZIP_UNCOMPRESS) {
      *error = fixscript_create_error_string(heap, "invalid handle");
      return fixscript_int(0);
   }

   if ((intptr_t)data == 0) {
      return fixscript_int(ch->read);
   }
   else {
      return fixscript_int(ch->written);
   }
}


static Value native_path_get_separator(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   return fixscript_int('\\');
#else
   return fixscript_int('/');
#endif
}


static Value native_path_get_prefix_length(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   Value val[3];
   int err, len;

   err = fixscript_get_array_length(heap, params[0], &len);
   if (!err) {
      if (len > 3) len = 3;
      err = fixscript_get_array_range(heap, params[0], 0, len, val);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   if (len >= 3 && ((val[0].value >= 'A' && val[0].value <= 'Z') || (val[0].value >= 'a' && val[0].value <= 'z')) && val[1].value == ':' && val[2].value == '\\') {
      return fixscript_int(3);
   }
   return fixscript_int(0);
#else
   Value val;
   int err;

   err = fixscript_get_array_elem(heap, params[0], 0, &val);
   if (err == FIXSCRIPT_ERR_OUT_OF_BOUNDS) {
      return fixscript_int(0);
   }
   if (err) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   return fixscript_int(val.value == '/'? 1 : 0);
#endif
}


static Value native_path_is_valid_name(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   return fixscript_int(1);
}


static Value native_path_get_current(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   DWORD len;
   uint16_t *s = NULL;
#else
   char *s = NULL;
#endif
   Value ret;

#if defined(_WIN32)
   len = GetCurrentDirectory(0, NULL);
   if (len <= 0 || len > 16*1024) {
      *error = fixscript_create_error_string(heap, "I/O error");
      goto error;
   }
   s = malloc((len+1)*sizeof(uint16_t));
   if (!s) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
   if (GetCurrentDirectory(len, s) != len-1) {
      *error = fixscript_create_error_string(heap, "I/O error");
      goto error;
   }
   ret = fixscript_create_string_utf16(heap, s, -1);
   if (!ret.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
#else
   s = getcwd(NULL, 0);
   if (!s) {
      *error = fixscript_create_error_string(heap, "I/O error");
      goto error;
   }

   ret = fixscript_create_string(heap, s, -1);
   if (!ret.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
#endif

   return ret;

error:
   free(s);
   return fixscript_int(0);
}


static Value native_path_get_roots(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value arr, str;
#if defined(_WIN32)
   DWORD len;
   uint16_t *buf = NULL, *p, *s;
#endif
   int err;

   arr = fixscript_create_array(heap, 0);
   if (!arr.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

#if defined(_WIN32)
   len = GetLogicalDriveStrings(0, NULL);
   if (len <= 0 || len > 16*1024) {
      *error = fixscript_create_error_string(heap, "I/O error");
      goto error;
   }
   buf = malloc((len+1)*sizeof(uint16_t));
   if (!buf) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
   if (GetLogicalDriveStrings(len, buf) != len-1) {
      *error = fixscript_create_error_string(heap, "I/O error");
      goto error;
   }
   s = buf;
   for (p=s; ; p++) {
      if (*p != 0) continue;
      if (p == s) break;

      str = fixscript_create_string_utf16(heap, s, p-s);
      if (!str.value) {
         fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
         goto error;
      }
      err = fixscript_append_array_elem(heap, arr, str);
      if (err) {
         fixscript_error(heap, error, err);
         goto error;
      }
      s = p+1;
   }
#else
   str = fixscript_create_string(heap, "/", -1);
   if (!str.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_append_array_elem(heap, arr, str);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
#endif

   return arr;

error:
#if defined(_WIN32)
   free(buf);
#endif
   return fixscript_int(0);
}


#if defined(_WIN32)
static int compare(const void *d1, const void *d2)
{
   const uint16_t **name1 = (void *)d1;
   const uint16_t **name2 = (void *)d2;
   return wcscmp(*name1, *name2);
}
#else
static int filter(const struct dirent *dirent)
{
   if (dirent->d_name[0] == '.') {
      if (strcmp(dirent->d_name, ".") == 0) return 0;
      if (strcmp(dirent->d_name, "..") == 0) return 0;
   }
   return 1;
}

#if defined(__APPLE__)
static int compare(const struct dirent **d1, const struct dirent **d2)
#else
static int compare(const void *d1, const void *d2)
#endif
{
   const struct dirent **e1 = (void *)d1;
   const struct dirent **e2 = (void *)d2;
   return strcmp((*e1)->d_name, (*e2)->d_name);
}
#endif


static Value native_path_get_files(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   HANDLE handle = INVALID_HANDLE_VALUE;
   WIN32_FIND_DATA fd;
   uint16_t **files = NULL, **new_files;
   Value arr, val, retval = fixscript_int(0);
   int i, cnt=0, cap=0;
   uint16_t *dirname = NULL, *dirname2 = NULL;
   uint16_t buf[256];
   int err;

   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &dirname, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   dirname2 = malloc((wcslen(dirname)+2+1)*sizeof(uint16_t));
   if (!dirname2) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   wcscpy(dirname2, dirname);
   wcscat(dirname2, L"\\*");

   handle = FindFirstFile(dirname2, &fd);
   if (handle == INVALID_HANDLE_VALUE) {
      err = GetLastError();
      if (err == ERROR_PATH_NOT_FOUND) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"path '%s' does not exist", dirname);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else if (err == ERROR_DIRECTORY) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"path '%s' is not a directory", dirname);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   do {
      if (fd.cFileName[0] == '.') {
         if (fd.cFileName[1] == 0) continue;
         if (fd.cFileName[1] == '.' && fd.cFileName[2] == 0) continue;
      }

      if (cnt == cap) {
         cap = cap? cap*2 : 8;
         new_files = realloc(files, cap * sizeof(Value));
         if (!new_files) {
            fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
            goto error;
         }
         files = new_files;
      }

      files[cnt] = wcsdup(fd.cFileName);
      if (!files[cnt]) {
         fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
         goto error;
      }
      cnt++;
   }
   while (FindNextFile(handle, &fd));

   qsort(files, cnt, sizeof(uint16_t *), compare);

   arr = fixscript_create_array(heap, cnt);
   if (!arr.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   for (i=0; i<cnt; i++) {
      val = fixscript_create_string_utf16(heap, files[i], -1);
      if (!val.value) {
         fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
         goto error;
      }

      err = fixscript_set_array_elem(heap, arr, i, val);
      if (err) {
         fixscript_error(heap, error, err);
         goto error;
      }
   }

   retval = arr;

error:
   free(dirname);
   free(dirname2);
   if (handle != INVALID_HANDLE_VALUE) {
      FindClose(handle);
   }
   for (i=0; i<cnt; i++) {
      free(files[i]);
   }
   free(files);
   return retval;
#else
   struct dirent **namelist = NULL;
   Value arr, val, retval = fixscript_int(0);
   char *dirname = NULL;
   int err, i, cnt=0;
   char buf[256];
   
   err = fixscript_get_string(heap, params[0], 0, -1, &dirname, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   cnt = scandir(dirname, &namelist, filter, compare);
   if (cnt < 0) {
      if (errno == ENOENT) {
         snprintf(buf, sizeof(buf), "path '%s' does not exist", dirname);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno == ENOTDIR) {
         snprintf(buf, sizeof(buf), "path '%s' is not a directory", dirname);
         *error = fixscript_create_error_string(heap, buf);
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   arr = fixscript_create_array(heap, cnt);
   if (!arr.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   for (i=0; i<cnt; i++) {
      val = fixscript_create_string(heap, namelist[i]->d_name, -1);
      if (!val.value) {
         fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
         goto error;
      }

      err = fixscript_set_array_elem(heap, arr, i, val);
      if (err) {
         fixscript_error(heap, error, err);
         goto error;
      }
   }

   retval = arr;

error:
   free(dirname);
   if (namelist) {
      for (i=0; i<cnt; i++) {
         free(namelist[i]);
      }
      free(namelist);
   }
   return retval;
#endif
}


static Value native_path_exists(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   uint16_t *path = NULL;
   Value retval = fixscript_int(0);
   uint16_t buf[256];
   int err;

   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
   
   if (GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) {
      err = GetLastError();
      if (err == ERROR_ACCESS_DENIED) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"access denied to '%s'", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
         goto error;
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   retval = fixscript_int(1);

error:
   free(path);
   return retval;
#else
   char *path = NULL;
   Value retval = fixscript_int(0);
   struct stat st;
   char buf[256];
   int err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   if (stat(path, &st) != 0) {
      if (errno == EACCES) {
         snprintf(buf, sizeof(buf), "access denied to '%s'", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno != ENOENT && errno != ENOTDIR) {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   retval = fixscript_int(1);

error:
   free(path);
   return retval;
#endif
}


static Value native_path_get_type(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   WIN32_FILE_ATTRIBUTE_DATA fad;
   uint16_t *path = NULL;
   Value retval = fixscript_int(0);
   uint16_t buf[256];
   int ret, err;

   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
   
   if (GetFileAttributesEx(path, GetFileExInfoStandard, &fad) == 0) {
      err = GetLastError();
      if (err == ERROR_ACCESS_DENIED) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"access denied to '%s'", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"path '%s' not found", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
      ret = TYPE_DIRECTORY;
   }
   else if (fad.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) {
      ret = TYPE_SPECIAL;
   }
   else {
      ret = TYPE_FILE;
   }

   retval = fixscript_int(ret);

error:
   free(path);
   return retval;
#else
   char *path = NULL;
   Value retval = fixscript_int(0);
   struct stat st, st2;
   char buf[256];
   int stat_ret, lstat_ret=0, ret, err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   stat_ret = stat(path, &st);
   lstat_ret = lstat(path, &st2);
   if (stat_ret != 0 || lstat_ret != 0) {
      if (errno == EACCES) {
         snprintf(buf, sizeof(buf), "access denied to '%s'", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno == ENOENT || errno == ENOTDIR) {
         if (stat_ret != 0 && lstat_ret == 0) {
            retval = fixscript_int(TYPE_SPECIAL | TYPE_SYMLINK);
            goto error;
         }
         snprintf(buf, sizeof(buf), "path '%s' not found", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   if (S_ISDIR(st.st_mode)) {
      ret = TYPE_DIRECTORY;
   }
   else if (S_ISREG(st.st_mode)) {
      ret = TYPE_FILE;
   }
   else {
      ret = TYPE_SPECIAL;
   }

   if (S_ISLNK(st2.st_mode)) {
      ret |= TYPE_SYMLINK;
   }

   retval = fixscript_int(ret);

error:
   free(path);
   return retval;
#endif
}


static Value native_path_get_length(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   WIN32_FILE_ATTRIBUTE_DATA fad;
   uint16_t *path = NULL;
   Value retval = fixscript_int(0);
   uint16_t buf[256];
   int err;

   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
   
   if (GetFileAttributesEx(path, GetFileExInfoStandard, &fad) == 0) {
      err = GetLastError();
      if (err == ERROR_ACCESS_DENIED) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"access denied to '%s'", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"path '%s' not found", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   *error = fixscript_int(fad.nFileSizeHigh);
   retval = fixscript_int(fad.nFileSizeLow);

error:
   free(path);
   return retval;
#else
   char *path = NULL;
   Value retval = fixscript_int(0);
   struct stat st;
   char buf[256];
   int err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   if (stat(path, &st) != 0) {
      if (errno == EACCES) {
         snprintf(buf, sizeof(buf), "access denied to '%s'", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno == ENOENT || errno == ENOTDIR) {
         if (lstat(path, &st) == 0) {
            goto error;
         }
         snprintf(buf, sizeof(buf), "path '%s' not found", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   if (S_ISREG(st.st_mode)) {
      *error = fixscript_int(((int64_t)st.st_size) >> 32);
      retval = fixscript_int((int64_t)st.st_size);
   }

error:
   free(path);
   return retval;
#endif
}


static Value native_path_get_modification_time(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   WIN32_FILE_ATTRIBUTE_DATA fad;
   uint16_t *path = NULL;
   Value retval = fixscript_int(0);
   uint16_t buf[256];
   int64_t time;
   int err;

   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
   
   if (GetFileAttributesEx(path, GetFileExInfoStandard, &fad) == 0) {
      err = GetLastError();
      if (err == ERROR_ACCESS_DENIED) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"access denied to '%s'", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
         snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"path '%s' not found", path);
         *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   time = (((uint64_t)fad.ftLastWriteTime.dwHighDateTime) << 32) | ((uint32_t)fad.ftLastWriteTime.dwLowDateTime);
   time = time / 10000000LL - 11644473600LL;

   *error = fixscript_int(((uint64_t)time) >> 32);
   retval = fixscript_int(time);

error:
   free(path);
   return retval;
#else
   char *path = NULL;
   Value retval = fixscript_int(0);
   struct stat st;
   char buf[256];
   int err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   if (stat(path, &st) != 0) {
      if (errno == EACCES) {
         snprintf(buf, sizeof(buf), "access denied to '%s'", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno == ENOENT || errno == ENOTDIR) {
         if (lstat(path, &st) == 0) {
            goto error;
         }
         snprintf(buf, sizeof(buf), "path '%s' not found", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   if (sizeof(time_t) == 4) {
      // treat time_t as unsigned to give chance for possible unsigned hack once year 2038 happens:
      *error = fixscript_int(0);
      retval = fixscript_int(st.st_mtime);
   }
   else {
      *error = fixscript_int(((int64_t)st.st_mtime) >> 32);
      retval = fixscript_int((int64_t)st.st_mtime);
   }

error:
   free(path);
   return retval;
#endif
}


static Value native_path_get_symlink(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   return fixscript_int(0);
#else
   char *path = NULL, *link = NULL;
   Value retval = fixscript_int(0);
   char buf[256];
   int err, len, new_len;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   len = 1;
again:
   link = malloc(len);
   if (!link) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
   new_len = readlink(path, link, len);
   if (new_len == len) {
      free(link);
      len *= 2;
      goto again;
   }
   len = new_len;

   if (len < 0) {
      if (errno == EACCES) {
         snprintf(buf, sizeof(buf), "access denied to '%s'", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno == ENOENT || errno == ENOTDIR) {
         snprintf(buf, sizeof(buf), "path '%s' not found", path);
         *error = fixscript_create_error_string(heap, buf);
      }
      else if (errno == EINVAL) {
         goto error;
      }
      else {
         *error = fixscript_create_error_string(heap, "I/O error");
      }
      goto error;
   }

   retval = fixscript_create_string(heap, link, len);
   if (!retval.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

error:
   free(path);
   free(link);
   return retval;
#endif
}


static Value native_path_create_directory(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#ifdef _WIN32
   uint16_t *path;
   uint16_t buf[256];
   int err;
   
   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (!CreateDirectory(path, NULL)) {
      snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"can't create directory '%s'", path);
      *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
   }
   free(path);
   return fixscript_int(0);
#else
   char *path;
   char buf[256];
   int err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (mkdir(path, 0777) != 0) {
      snprintf(buf, sizeof(buf), "can't create directory '%s'", path);
      *error = fixscript_create_error_string(heap, buf);
   }
   free(path);
   return fixscript_int(0);
#endif
}


static Value native_path_delete_file(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#ifdef _WIN32
   uint16_t *path;
   uint16_t buf[256];
   int err;
   
   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (!DeleteFile(path)) {
      snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"can't delete file '%s'", path);
      *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
   }
   free(path);
   return fixscript_int(0);
#else
   char *path;
   char buf[256];
   int err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (unlink(path) != 0) {
      snprintf(buf, sizeof(buf), "can't delete file '%s'", path);
      *error = fixscript_create_error_string(heap, buf);
   }
   free(path);
   return fixscript_int(0);
#endif
}


static Value native_path_delete_directory(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#ifdef _WIN32
   uint16_t *path;
   uint16_t buf[256];
   int err;
   
   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (!RemoveDirectory(path)) {
      snwprintf(buf, sizeof(buf)/sizeof(uint16_t)-1, L"can't delete directory '%s'", path);
      *error = fixscript_create_error(heap, fixscript_create_string_utf16(heap, buf, -1));
   }
   free(path);
   return fixscript_int(0);
#else
   char *path;
   char buf[256];
   int err;
   
   err = fixscript_get_string(heap, params[0], 0, -1, &path, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (rmdir(path) != 0) {
      snprintf(buf, sizeof(buf), "can't delete directory '%s'", path);
      *error = fixscript_create_error_string(heap, buf);
   }
   free(path);
   return fixscript_int(0);
#endif
}


static void *file_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   FileHandle *handle = p1;
   switch (op) {
      case HANDLE_OP_FREE:
         if (__sync_sub_and_fetch(&handle->refcnt, 1) == 0) {
            if (!handle->closed) {
               #if defined(_WIN32)
                  CloseHandle(handle->handle);
               #else
                  close(handle->fd);
               #endif
            }
            free(handle);
         }
         break;

      case HANDLE_OP_COPY:
         __sync_add_and_fetch(&handle->refcnt, 1);
         return handle;
   }
   return NULL;
}


static Value native_file_open(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle = NULL;
   Value retval = fixscript_int(0);
#if defined(_WIN32)
   uint16_t *fname_utf16 = NULL;
#endif
   char *fname = NULL, *s;
   int mode = fixscript_get_int(params[1]);
   int err, len, flags;

   if ((mode & (SCRIPT_FILE_READ | SCRIPT_FILE_WRITE)) == 0) {
      *error = fixscript_create_error_string(heap, "file must be opened for reading and/or writing");
      goto error;
   }

   if ((mode & (SCRIPT_FILE_READ | SCRIPT_FILE_WRITE | SCRIPT_FILE_CREATE)) == (SCRIPT_FILE_READ | SCRIPT_FILE_CREATE)) {
      *error = fixscript_create_error_string(heap, "invalid mode");
      goto error;
   }

#if defined(_WIN32)
   err = fixscript_get_string_utf16(heap, params[0], 0, -1, &fname_utf16, NULL);
#else
   err = 0;
#endif
   if (!err) {
      err = fixscript_get_string(heap, params[0], 0, -1, &fname, NULL);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   handle = calloc(1, sizeof(FileHandle));
   if (!handle) goto error;
   handle->refcnt = 1;
   handle->mode = mode;

#if defined(_WIN32)
   flags = 0;
   if (mode & SCRIPT_FILE_READ) flags |= GENERIC_READ;
   if (mode & SCRIPT_FILE_WRITE) flags |= GENERIC_WRITE;
   handle->handle = CreateFile(fname_utf16, flags, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, (mode & SCRIPT_FILE_CREATE)? CREATE_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   if (handle->handle == INVALID_HANDLE_VALUE) {
      len = strlen(fname)+64;
      s = malloc(len);
      snprintf(s, len, "can't open file '%s'", fname);
      *error = fixscript_create_error_string(heap, s);
      free(s);
      goto error;
   }
#else
   switch (mode & (SCRIPT_FILE_READ | SCRIPT_FILE_WRITE)) {
      case SCRIPT_FILE_READ: flags = O_RDONLY; break;
      case SCRIPT_FILE_WRITE: flags = O_WRONLY; break;
      default: flags = O_RDWR; break;
   }
   if (mode & SCRIPT_FILE_CREATE) {
      flags |= O_CREAT | O_TRUNC;
   }
   
   handle->fd = open(fname, flags, 0666);
   if (handle->fd == -1) {
      len = strlen(fname)+64;
      s = malloc(len);
      snprintf(s, len, "can't open file '%s'", fname);
      *error = fixscript_create_error_string(heap, s);
      free(s);
      goto error;
   }
#endif

   retval = fixscript_create_value_handle(heap, HANDLE_TYPE_FILE, handle, file_handle_func);
   handle = NULL;
   if (!retval.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

error:
   free(fname);
#if defined(_WIN32)
   free(fname_utf16);
   if (handle && handle->handle != INVALID_HANDLE_VALUE) {
      CloseHandle(handle->handle);
   }
#else
   if (handle && handle->fd != -1) {
      close(handle->fd);
   }
#endif
   free(handle);
   return retval;
}


static FileHandle *get_file_handle(Heap *heap, Value *error, Value handle_val)
{
   FileHandle *handle;

   handle = fixscript_get_handle(heap, handle_val, HANDLE_TYPE_FILE, NULL);
   if (!handle) {
      *error = fixscript_create_error_string(heap, "invalid file handle");
      return NULL;
   }

   if (handle->closed) {
      *error = fixscript_create_error_string(heap, "file already closed");
      return NULL;
   }

   return handle;
}


static Value native_file_close(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle;

   handle = get_file_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

   handle->closed = 1;
#if defined(_WIN32)
   if (!CloseHandle(handle->handle)) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#else
   if (close(handle->fd) == -1) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#endif
   return fixscript_int(0);
}


static Value native_file_read(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle;
   char *buf;
#if defined(_WIN32)
   DWORD ret;
#else
   ssize_t ret;
#endif
   int err, len;

   handle = get_file_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

   if ((handle->mode & SCRIPT_FILE_READ) == 0) {
      *error = fixscript_create_error_string(heap, "file not opened for reading");
      return fixscript_int(0);
   }

   len = params[3].value;
   err = fixscript_lock_array(heap, params[1], params[2].value, len, (void **)&buf, 1, 0);
   if (err) {
      fixscript_error(heap, error, err);
      return fixscript_int(0);
   }

#if defined(_WIN32)
   if (!ReadFile(handle->handle, buf, params[3].value, &ret, NULL)) {
      ret = -1;
   }
#else
   ret = read(handle->fd, buf, params[3].value);
   if (ret < 0) {
      len = 0;
   }
   else if (ret < len) {
      len = ret;
   }
#endif

   fixscript_unlock_array(heap, params[1], params[2].value, len, (void **)&buf, 1, 1);

   if (ret == 0) {
      return fixscript_int(-1);
   }
   if (ret < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   return fixscript_int((int)ret);
}


static Value native_file_write(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle;
   char *buf;
#if defined(_WIN32)
   DWORD ret;
#else
   ssize_t ret = 0;
#endif
   int err;

   handle = get_file_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

   if ((handle->mode & SCRIPT_FILE_WRITE) == 0) {
      *error = fixscript_create_error_string(heap, "file not opened for writing");
      return fixscript_int(0);
   }

   err = fixscript_lock_array(heap, params[1], params[2].value, params[3].value, (void **)&buf, 1, 1);
   if (err) {
      fixscript_error(heap, error, err);
      return fixscript_int(0);
   }

#if defined(_WIN32)
   if (!WriteFile(handle->handle, buf, params[3].value, &ret, NULL)) {
      ret = -1;
   }
#else
   ret = write(handle->fd, buf, params[3].value);
   if (ret < 0 && errno == EAGAIN) {
      ret = 0;
   }
#endif

   fixscript_unlock_array(heap, params[1], params[2].value, params[3].value, (void **)&buf, 1, 0);

   if (ret < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   return fixscript_int(ret);
}


static Value native_file_get_length(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle;
#if defined(_WIN32)
   LARGE_INTEGER size;
#else
   off_t cur_pos, end_pos;
#endif

   handle = get_file_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

#if defined(_WIN32)
   if (!GetFileSizeEx(handle->handle, &size)) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   *error = fixscript_int(size.HighPart);
   return fixscript_int(size.LowPart);
#else
   cur_pos = lseek(handle->fd, 0, SEEK_CUR);
   if (cur_pos < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   end_pos = lseek(handle->fd, 0, SEEK_END);
   if (end_pos < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   if (lseek(handle->fd, cur_pos, SEEK_SET) < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   *error = fixscript_int(((int64_t)end_pos) >> 32);
   return fixscript_int((int64_t)end_pos);
#endif
}


static Value native_file_get_position(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle;
#if defined(_WIN32)
   LARGE_INTEGER zero, pos;
#else
   off_t pos;
#endif

   handle = get_file_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

#if defined(_WIN32)
   zero.LowPart = 0;
   zero.HighPart = 0;
   if (!SetFilePointerEx(handle->handle, zero, &pos, FILE_CURRENT)) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   *error = fixscript_int(pos.HighPart);
   return fixscript_int(pos.LowPart);
#else
   pos = lseek(handle->fd, 0, SEEK_CUR);
   if (pos < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   *error = fixscript_int(((int64_t)pos) >> 32);
   return fixscript_int((int64_t)pos);
#endif
}


static Value native_file_set_position(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   FileHandle *handle;
#if defined(_WIN32)
   LARGE_INTEGER pos;
#else
   off_t pos;
#endif

   handle = get_file_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

#if defined(_WIN32)
   pos.LowPart = fixscript_get_int(params[1]);
   pos.HighPart = fixscript_get_int(params[2]);
   if (!SetFilePointerEx(handle->handle, pos, NULL, FILE_BEGIN)) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#else
   pos = ((uint32_t)params[1].value) | (((uint64_t)params[2].value) << 32);

   if (lseek(handle->fd, pos, SEEK_SET) < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#endif
   
   return fixscript_int(0);
}


static Value native_file_exists(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   *error = fixscript_create_error_string(heap, "not implemented yet");
   return fixscript_int(0);
}


static void *tcp_connection_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   TCPConnectionHandle *handle = p1;
   switch (op) {
      case HANDLE_OP_FREE:
         if (__sync_sub_and_fetch(&handle->refcnt, 1) == 0) {
            if (!handle->closed) {
               #if defined(_WIN32)
                  closesocket(handle->socket);
               #else
                  close(handle->fd);
               #endif
            }
            free(handle);
         }
         break;

      case HANDLE_OP_COPY:
         __sync_add_and_fetch(&handle->refcnt, 1);
         return handle;
   }
   return NULL;
}


static TCPConnectionHandle *get_tcp_connection_handle(Heap *heap, Value *error, Value handle_val)
{
   TCPConnectionHandle *handle;

   handle = fixscript_get_handle(heap, handle_val, HANDLE_TYPE_TCP_CONNECTION, NULL);
   if (!handle) {
      *error = fixscript_create_error_string(heap, "invalid TCP connection handle");
      return NULL;
   }

   if (handle->closed) {
      *error = fixscript_create_error_string(heap, "TCP connection already closed");
      return NULL;
   }

   return handle;
}


static int update_nonblocking(TCPConnectionHandle *handle)
{
#if defined(_WIN32)
   u_long arg;
#else
   int flags;
#endif

   if (handle->in_nonblocking != handle->want_nonblocking) {
      #if defined(_WIN32)
         arg = handle->want_nonblocking;
         if (ioctlsocket(handle->socket, FIONBIO, &arg) != 0) {
            return 0;
         }
      #else
         flags = fcntl(handle->fd, F_GETFL);
         if (flags == -1) {
            return 0;
         }
         if (handle->want_nonblocking) {
            flags |= O_NONBLOCK;
         }
         else {
            flags &= ~O_NONBLOCK;
         }
         if (fcntl(handle->fd, F_SETFL, flags) == -1) {
            return 0;
         }
      #endif
      handle->in_nonblocking = handle->want_nonblocking;
   }
   return 1;
}


#if defined(_WIN32)
static int tcp_connect(const char *hostname, int port, SOCKET *ret, int overlapped)
#else
static int tcp_connect(const char *hostname, int port, int *ret)
#endif
{
#if defined(_WIN32)
   LPHOSTENT host_ent;
   SOCKADDR_IN sock_addr;
   SOCKET sock = INVALID_SOCKET;
#else
   struct addrinfo *addrinfo = NULL, *ai, hints;
   int fd = -1;
   char buf[16];
   int err;
#endif
   int flag;

#if defined(_WIN32)
   host_ent = gethostbyname(hostname);
   if (!host_ent || !host_ent->h_addr_list[0]) {
      goto error;
   }

   if (overlapped) {
      sock = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
   }
   else {
      sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
   }
   if (sock == INVALID_SOCKET) {
      goto error;
   }

   memset(&sock_addr, 0, sizeof(SOCKADDR_IN));
   sock_addr.sin_family = PF_INET;
   sock_addr.sin_addr = *((LPIN_ADDR)host_ent->h_addr_list[0]);
   sock_addr.sin_port = htons(port);

   if (connect(sock, (LPSOCKADDR)&sock_addr, sizeof(SOCKADDR_IN)) != 0) {
      goto error;
   }

   flag = 1;
   setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));

   *ret = sock;
#else
   memset(&hints, 0, sizeof(struct addrinfo));
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;

   snprintf(buf, sizeof(buf), "%d", port);
   err = getaddrinfo(hostname, buf, &hints, &addrinfo);
   if (err != 0) {
      goto error;
   }

   for (ai = addrinfo; ai; ai = ai->ai_next) {
      fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
      if (fd == -1) {
         continue;
      }

      if (connect(fd, ai->ai_addr, ai->ai_addrlen) != 0) {
         close(fd);
         fd = -1;
         continue;
      }

      break;
   }

   if (fd == -1) {
      goto error;
   }

   flag = 1;
   setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));

   *ret = fd;
#endif

   return 1;

error:
#if defined(_WIN32)
   if (sock != INVALID_SOCKET) {
      closesocket(sock);
   }
#else
   if (addrinfo) {
      freeaddrinfo(addrinfo);
   }
   if (fd != -1) {
      close(fd);
   }
#endif
   return 0;
}
 

static Value native_tcp_connection_open(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   TCPConnectionHandle *handle;
   char *hostname = NULL;
   char buf[128];
   int err;
   Value retval = fixscript_int(0);
#if defined(_WIN32)
   SOCKET sock = INVALID_SOCKET;
#else
   int fd = -1;
#endif

   err = fixscript_get_string(heap, params[0], 0, -1, &hostname, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

#if defined(_WIN32)
   if (!tcp_connect(hostname, params[1].value, &sock, 0))
#else
   if (!tcp_connect(hostname, params[1].value, &fd))
#endif
   {
      snprintf(buf, sizeof(buf), "can't connect to %s:%d", hostname, params[1].value);
      *error = fixscript_create_error_string(heap, buf);
      goto error;
   }

   handle = calloc(1, sizeof(TCPConnectionHandle));
   if (!handle) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   handle->refcnt = 1;
#if defined(_WIN32)
   handle->socket = sock;
#else
   handle->fd = fd;
#endif

   retval = fixscript_create_value_handle(heap, HANDLE_TYPE_TCP_CONNECTION, handle, tcp_connection_handle_func);
#if defined(_WIN32)
   sock = INVALID_SOCKET;
#else
   fd = -1;
#endif
   if (!retval.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

error:
   free(hostname);
#if defined(_WIN32)
   if (!retval.value && sock != INVALID_SOCKET) {
      closesocket(sock);
   }
#else
   if (!retval.value && fd != -1) {
      close(fd);
   }
#endif
   return retval;
}


static Value native_tcp_connection_close(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   TCPConnectionHandle *handle;

   handle = get_tcp_connection_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

   handle->closed = 1;
#if defined(_WIN32)
   if (closesocket(handle->socket) != 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#else
   if (close(handle->fd) == -1) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#endif
   return fixscript_int(0);
}


static Value native_tcp_connection_read(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   TCPConnectionHandle *handle;
   char *buf;
   int timeout = fixscript_get_int(params[4]);
#if defined(_WIN32)
   fd_set readfds;
   TIMEVAL timeval;
   int ret;
#else
   struct pollfd pfd;
   ssize_t ret;
#endif
   int err, len;

   handle = get_tcp_connection_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

#if defined(_WIN32)
   if (timeout >= 0) {
      FD_ZERO(&readfds);
      FD_SET(handle->socket, &readfds);
      timeval.tv_sec = timeout / 1000;
      timeval.tv_usec = (timeout % 1000) * 1000;
      ret = select(1, &readfds, NULL, NULL, &timeval);
      if (ret == SOCKET_ERROR) {
         *error = fixscript_create_error_string(heap, "I/O error");
         return fixscript_int(0);
      }
      if (ret == 0) {
         return fixscript_int(0);
      }
      handle->want_nonblocking = 1;
   }
#else
   if (timeout >= 0) {
      pfd.fd = handle->fd;
      pfd.events = POLLIN;
      ret = poll(&pfd, 1, timeout);
      if (ret < 0) {
         *error = fixscript_create_error_string(heap, "I/O error");
         return fixscript_int(0);
      }
      if (ret == 1) {
         ret = 0;
         if (pfd.revents & POLLIN) ret = 1;
      }
      if (ret == 0) {
         return fixscript_int(0);
      }
      handle->want_nonblocking = 1;
   }
#endif

   if (!update_nonblocking(handle)) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   handle->want_nonblocking = 0;

   len = params[3].value;
   err = fixscript_lock_array(heap, params[1], params[2].value, len, (void **)&buf, 1, 0);
   if (err) {
      fixscript_error(heap, error, err);
      return fixscript_int(0);
   }

#if defined(_WIN32)
   ret = recv(handle->socket, buf, len, 0);
   if (ret == SOCKET_ERROR) {
      len = 0;
      ret = -1;
   }
   else if (ret < len) {
      len = ret;
   }
#else
   ret = read(handle->fd, buf, len);
   if (ret < 0) {
      len = 0;
   }
   else if (ret < len) {
      len = ret;
   }
#endif

   fixscript_unlock_array(heap, params[1], params[2].value, len, (void **)&buf, 1, 1);

   if (ret == 0) {
      return fixscript_int(-1);
   }
   if (ret < 0) {
      #if !defined(_WIN32)
      if (errno == EAGAIN) {
         return fixscript_int(0);
      }
      #endif
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   return fixscript_int((int)ret);
}


static Value native_tcp_connection_write(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   TCPConnectionHandle *handle;
   char *buf;
   int timeout = fixscript_get_int(params[4]);
#if defined(_WIN32)
   fd_set writefds;
   TIMEVAL timeval;
   int ret;
#else
   struct pollfd pfd;
   ssize_t ret;
#endif
   int err;

   handle = get_tcp_connection_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

#if defined(_WIN32)
   if (timeout >= 0) {
      FD_ZERO(&writefds);
      FD_SET(handle->socket, &writefds);
      timeval.tv_sec = timeout / 1000;
      timeval.tv_usec = (timeout % 1000) * 1000;
      ret = select(1, NULL, &writefds, NULL, &timeval);
      if (ret == SOCKET_ERROR) {
         *error = fixscript_create_error_string(heap, "I/O error");
         return fixscript_int(0);
      }
      if (ret == 0) {
         return fixscript_int(0);
      }
      handle->want_nonblocking = 1;
   }
#else
   if (timeout >= 0) {
      pfd.fd = handle->fd;
      pfd.events = POLLOUT;
      ret = poll(&pfd, 1, timeout);
      if (ret < 0) {
         *error = fixscript_create_error_string(heap, "I/O error");
         return fixscript_int(0);
      }
      if (ret == 1) {
         ret = 0;
         if (pfd.revents & POLLOUT) ret = 1;
      }
      if (ret == 0) {
         return fixscript_int(0);
      }
      handle->want_nonblocking = 1;
   }
#endif

   if (!update_nonblocking(handle)) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   handle->want_nonblocking = 0;

   err = fixscript_lock_array(heap, params[1], params[2].value, params[3].value, (void **)&buf, 1, 1);
   if (err) {
      fixscript_error(heap, error, err);
      return fixscript_int(0);
   }

#if defined(_WIN32)
   ret = send(handle->socket, buf, params[3].value, 0);
   if (ret == SOCKET_ERROR) {
      if (WSAGetLastError() == WSAEWOULDBLOCK) {
         ret = 0;
      }
      else {
         ret = -1;
      }
   }
#else
   ret = write(handle->fd, buf, params[3].value);
   if (ret < 0 && errno == EAGAIN) {
      ret = 0;
   }
#endif

   fixscript_unlock_array(heap, params[1], params[2].value, params[3].value, (void **)&buf, 1, 0);

   if (ret < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   return fixscript_int(ret);
}


static void *tcp_server_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   TCPServerHandle *handle = p1;
   switch (op) {
      case HANDLE_OP_FREE:
         if (__sync_sub_and_fetch(&handle->refcnt, 1) == 0) {
            if (!handle->closed) {
               #if defined(_WIN32)
                  closesocket(handle->socket);
               #else
                  close(handle->fd);
               #endif
            }
            free(handle);
         }
         break;

      case HANDLE_OP_COPY:
         __sync_add_and_fetch(&handle->refcnt, 1);
         return handle;
   }
   return NULL;
}


static TCPServerHandle *get_tcp_server_handle(Heap *heap, Value *error, Value handle_val)
{
   TCPServerHandle *handle;

   handle = fixscript_get_handle(heap, handle_val, HANDLE_TYPE_TCP_SERVER, NULL);
   if (!handle) {
      *error = fixscript_create_error_string(heap, "invalid TCP server handle");
      return NULL;
   }

   if (handle->closed) {
      *error = fixscript_create_error_string(heap, "TCP server already closed");
      return NULL;
   }

   return handle;
}


static Value native_tcp_server_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int local_only = data == (void *)1;
   TCPServerHandle *handle;
   struct sockaddr_in server;
#if defined(_WIN32)
   SOCKET sock = INVALID_SOCKET;
#else
   int fd = -1;
#endif
   int reuse;
   Value retval = fixscript_int(0);

#if defined(_WIN32)
   sock = socket(AF_INET, SOCK_STREAM, 0);
   if (sock == INVALID_SOCKET) {
      goto io_error;
   }

   reuse = 1;
   if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
      goto io_error;
   }

   server.sin_family = AF_INET;
   server.sin_addr.s_addr = htonl(local_only? INADDR_LOOPBACK : INADDR_ANY);
   server.sin_port = htons(params[0].value);

   if (bind(sock, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR) {
      goto io_error;
   }

   if (listen(sock, 5) == SOCKET_ERROR) {
      goto io_error;
   }
#else
   fd = socket(AF_INET, SOCK_STREAM, 0);
   if (fd == -1) {
      goto io_error;
   }

   reuse = 1;
   if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
      goto io_error;
   }

   server.sin_family = AF_INET;
   server.sin_addr.s_addr = htonl(local_only? INADDR_LOOPBACK : INADDR_ANY);
   server.sin_port = htons(params[0].value);

   if (bind(fd, (struct sockaddr *)&server, sizeof(server)) < 0) {
      goto io_error;
   }

   if (listen(fd, 5) < 0) {
      goto io_error;
   }
#endif

   handle = calloc(1, sizeof(TCPServerHandle));
   if (!handle) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   handle->refcnt = 1;
#if defined(_WIN32)
   handle->socket = sock;
#else
   handle->fd = fd;
#endif

   retval = fixscript_create_value_handle(heap, HANDLE_TYPE_TCP_SERVER, handle, tcp_server_handle_func);
#if defined(_WIN32)
   sock = INVALID_SOCKET;
#else
   fd = -1;
#endif
   if (!retval.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

error:
#if defined(_WIN32)
   if (!retval.value && sock != INVALID_SOCKET) {
      closesocket(sock);
   }
#else
   if (!retval.value && fd != -1) {
      close(fd);
   }
#endif
   return retval;

io_error:
   *error = fixscript_create_error_string(heap, "I/O error");
   goto error;
}


static Value native_tcp_server_close(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   TCPServerHandle *handle;

   handle = get_tcp_server_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

   handle->closed = 1;
#if defined(_WIN32)
   if (closesocket(handle->socket) != 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#else
   if (close(handle->fd) == -1) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#endif
   return fixscript_int(0);
}


static Value native_tcp_server_accept(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   TCPServerHandle *handle;
   TCPConnectionHandle *conn_handle;
   Value retval;
   int timeout = fixscript_get_int(params[1]);
#if defined(_WIN32)
   fd_set readfds;
   TIMEVAL timeval;
   SOCKET sock;
#else
   struct pollfd pfd;
   int fd;
#endif
   int ret, flag;

   handle = get_tcp_server_handle(heap, error, params[0]);
   if (!handle) {
      return fixscript_int(0);
   }

#if defined(_WIN32)
   if (timeout >= 0) {
      FD_ZERO(&readfds);
      FD_SET(handle->socket, &readfds);
      timeval.tv_sec = timeout / 1000;
      timeval.tv_usec = (timeout % 1000) * 1000;
      ret = select(1, &readfds, NULL, NULL, &timeval);
      if (ret == SOCKET_ERROR) {
         *error = fixscript_create_error_string(heap, "I/O error");
         return fixscript_int(0);
      }
      if (ret == 0) {
         return fixscript_int(0);
      }
   }
   sock = accept(handle->socket, NULL, NULL);
   if (sock == INVALID_SOCKET) {
      if (WSAGetLastError() == WSAEWOULDBLOCK) {
         return fixscript_int(0);
      }
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   flag = 1;
   setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
#else
   if (timeout >= 0) {
      pfd.fd = handle->fd;
      pfd.events = POLLIN;
      ret = poll(&pfd, 1, timeout);
      if (ret < 0) {
         *error = fixscript_create_error_string(heap, "I/O error");
         return fixscript_int(0);
      }
      if (ret == 1) {
         ret = 0;
         if (pfd.revents & POLLIN) ret = 1;
      }
      if (ret == 0) {
         return fixscript_int(0);
      }
   }
   fd = accept(handle->fd, NULL, NULL);
   if (fd < 0) {
      if (errno == EAGAIN) {
         return fixscript_int(0);
      }
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }

   flag = 1;
   setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
#endif

   conn_handle = calloc(1, sizeof(TCPConnectionHandle));
   if (!conn_handle) {
      #if defined(_WIN32)
         closesocket(sock);
      #else
         close(fd);
      #endif
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      return fixscript_int(0);
   }

   conn_handle->refcnt = 1;
#if defined(_WIN32)
   conn_handle->socket = sock;
#else
   conn_handle->fd = fd;
#endif

   retval = fixscript_create_value_handle(heap, HANDLE_TYPE_TCP_CONNECTION, conn_handle, tcp_connection_handle_func);
   if (!retval.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      return fixscript_int(0);
   }

   return retval;
}


static void async_process_ref(AsyncProcess *proc)
{
   __sync_add_and_fetch(&proc->refcnt, 1);
}


static void async_process_unref(AsyncProcess *proc)
{
   AsyncThreadResult *atr, *atr_next;
   AsyncTimer *timer, *timer_next;
   
   if (__sync_sub_and_fetch(&proc->refcnt, 1) == 0) {
      for (atr = proc->thread_results; atr; atr = atr_next) {
         #if defined(_WIN32)
            if (atr->socket != INVALID_SOCKET) {
               closesocket(atr->socket);
            }
         #else
            if (atr->fd != -1) {
               close(atr->fd);
            }
         #endif
         atr_next = atr->next;
         free(atr);
      }
      #if defined(_WIN32)
         CloseHandle(proc->iocp);
      #else
         poll_destroy(proc->poll);
      #endif
      for (timer = proc->timers; timer; timer = timer_next) {
         timer_next = timer->next;
         free(timer);
      }
      pthread_mutex_destroy(&proc->mutex);
      free(proc);
   }
}


static AsyncProcess *get_async_process(Heap *heap, Value *error)
{
   AsyncProcess *proc;
   int err;

   proc = fixscript_get_heap_data(heap, async_process_key);
   if (!proc) {
      proc = calloc(1, sizeof(AsyncProcess));
      proc->refcnt = 1;
      if (pthread_mutex_init(&proc->mutex, NULL) != 0) {
         free(proc);
         *error = fixscript_create_error_string(heap, "can't create mutex");
         return NULL;
      }

#if defined(_WIN32)
      proc->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
      if (!proc->iocp) {
         pthread_mutex_destroy(&proc->mutex);
         free(proc);
         *error = fixscript_create_error_string(heap, "can't create IO completion port");
         return NULL;
      }
#else
      proc->poll = poll_create();
      if (!proc->poll) {
         pthread_mutex_destroy(&proc->mutex);
         free(proc);
         *error = fixscript_create_error_string(heap, "can't create poll file descriptor");
         return NULL;
      }
#endif

      err = fixscript_set_heap_data(heap, async_process_key, proc, (HandleFreeFunc)async_process_unref);
      if (err) {
         fixscript_error(heap, error, err);
         return NULL;
      }
   }
   return proc;
}


static void async_process_notify(AsyncProcess *proc)
{
#if defined(_WIN32)
   PostQueuedCompletionStatus(proc->iocp, 0, 0, (OVERLAPPED *)proc);
#else
   poll_interrupt(proc->poll);
#endif
}


#if defined(_WIN32)
static DWORD WINAPI thread_main(void *data)
#else
static void *thread_main(void *data)
#endif
{
   Thread *thread = data;

   for (;;) {
      pthread_mutex_lock(&thread->mutex);
      while (!thread->func) {
         if (pthread_cond_timedwait_relative(&thread->cond, &thread->mutex, 5000*1000000LL) == ETIMEDOUT && !thread->func) {
            pthread_mutex_unlock(&thread->mutex);
            goto end;
         }
      }

      thread->func(thread->data);
      thread->func = NULL;
      pthread_mutex_unlock(&thread->mutex);
      
      pthread_mutex_lock(threads_mutex);
      thread->next = threads_pool;
      threads_pool = thread;
      pthread_mutex_unlock(threads_mutex);
   }

end:
   pthread_cond_destroy(&thread->cond);
   pthread_mutex_destroy(&thread->mutex);
   free(thread);

#if defined(_WIN32)
   return 0;
#else
   return NULL;
#endif
}


static int async_run_thread(ThreadFunc func, void *data)
{
   pthread_mutex_t *mutex, *cur_mutex;
   Thread *thread;
#if defined(_WIN32)
   HANDLE handle;
#else
   pthread_t handle;
#endif

   mutex = threads_mutex;
   if (!mutex) {
      mutex = calloc(1, sizeof(pthread_mutex_t));
      if (!mutex) return 0;
      if (pthread_mutex_init(mutex, NULL) != 0) {
         free(mutex);
         return 0;
      }
      cur_mutex = __sync_val_compare_and_swap(&threads_mutex, NULL, mutex);
      if (cur_mutex) {
         pthread_mutex_destroy(mutex);
         free(mutex);
         mutex = cur_mutex;
      }
   }
   
   pthread_mutex_lock(mutex);

   if (threads_pool) {
      thread = threads_pool;
      threads_pool = thread->next;
   }
   else {
      thread = calloc(1, sizeof(Thread));
      if (!thread) {
         pthread_mutex_unlock(mutex);
         return 0;
      }

      if (pthread_mutex_init(&thread->mutex, NULL) != 0) {
         free(thread);
         pthread_mutex_unlock(mutex);
         return 0;
      }

      if (pthread_cond_init(&thread->cond, NULL) != 0) {
         pthread_mutex_destroy(&thread->mutex);
         free(thread);
         pthread_mutex_unlock(mutex);
         return 0;
      }

#if defined(_WIN32)
      handle = CreateThread(NULL, 0, thread_main, thread, 0, NULL);
      if (!handle) {
         pthread_cond_destroy(&thread->cond);
         pthread_mutex_destroy(&thread->mutex);
         free(thread);
         pthread_mutex_unlock(mutex);
         return 0;
      }
      CloseHandle(handle);
#else
      if (pthread_create(&handle, NULL, thread_main, thread) != 0) {
         pthread_cond_destroy(&thread->cond);
         pthread_mutex_destroy(&thread->mutex);
         free(thread);
         pthread_mutex_unlock(mutex);
         return 0;
      }
      pthread_detach(handle);
#endif
   }

   pthread_mutex_unlock(mutex);
   
   pthread_mutex_lock(&thread->mutex);
   thread->func = func;
   thread->data = data;
   pthread_cond_signal(&thread->cond);
   pthread_mutex_unlock(&thread->mutex);
   return 1;
}


static void free_async_handle(void *data)
{
   AsyncHandle *handle = data;
#ifndef _WIN32
   AsyncProcess *proc = handle->proc;
#endif

#ifndef _WIN32
   poll_remove_socket(proc->poll, handle->fd);
#endif
   async_process_unref(handle->proc);

   if (handle->type == ASYNC_TCP_CONNECTION || handle->type == ASYNC_TCP_SERVER) {
      #ifdef _WIN32
      if (handle->type == ASYNC_TCP_SERVER) {
         closesocket(((AsyncServerHandle *)handle)->accept_socket);
      }
      closesocket(handle->socket);
      #else
      close(handle->fd);
      #endif
   }
   free(handle);
}


#ifndef _WIN32
static void update_poll_ctl(AsyncHandle *handle)
{
   AsyncServerHandle *server_handle;

   if (handle->type == ASYNC_TCP_CONNECTION) {
      if (handle->active != handle->last_active) {
         poll_update_socket(handle->proc->poll, handle->fd, handle, handle->active);
         handle->last_active = handle->active;
      }
   }
   else if (handle->type == ASYNC_TCP_SERVER) {
      server_handle = (AsyncServerHandle *)handle;
      if (server_handle->active != server_handle->last_active) {
         poll_update_socket(server_handle->proc->poll, server_handle->fd, server_handle, server_handle->active? ASYNC_READ : 0);
         server_handle->last_active = server_handle->active;
      }
   }
}
#endif


typedef struct {
   AsyncProcess *proc;
   char *hostname;
   int port;
   Value callback;
   Value data;
   AsyncThreadResult *atr;
} TCPOpenData;

static void tcp_open_func(void *data)
{
   TCPOpenData *tod = data;
   AsyncProcess *proc = tod->proc;
   AsyncThreadResult *atr = tod->atr;
#if defined(_WIN32)
   u_long arg;
#else
   int flags;
#endif

   atr->type = ASYNC_TCP_CONNECTION;
   atr->callback = tod->callback;
   atr->data = tod->data;

#if defined(_WIN32)
   if (tcp_connect(tod->hostname, tod->port, &atr->socket, 1)) {
      arg = 1;
      if (ioctlsocket(atr->socket, FIONBIO, &arg) != 0) {
         closesocket(atr->socket);
         atr->socket = INVALID_SOCKET;
      }
   }
   else {
      atr->socket = INVALID_SOCKET;
   }
#else
   if (tcp_connect(tod->hostname, tod->port, &atr->fd)) {
      flags = fcntl(atr->fd, F_GETFL);
      if (flags != -1) {
         flags |= O_NONBLOCK;
         if (fcntl(atr->fd, F_SETFL, flags) == -1) {
            close(atr->fd);
            atr->fd = -1;
         }
      }
      else {
         close(atr->fd);
         atr->fd = -1;
      }
   }
   else {
      atr->fd = -1;
   }
#endif

   pthread_mutex_lock(&proc->mutex);
   atr->next = proc->thread_results;
   proc->thread_results = atr;
   async_process_notify(proc);
   pthread_mutex_unlock(&proc->mutex);

   async_process_unref(tod->proc);
   free(tod->hostname);
   free(tod);
}

static Value native_async_tcp_connection_open(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncProcess *proc;
   TCPOpenData *tod;
   int err;
   
   proc = get_async_process(heap, error);
   if (!proc) return fixscript_int(0);

   tod = calloc(1, sizeof(TCPOpenData));
   if (!tod) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   
   tod->atr = calloc(1, sizeof(AsyncThreadResult));
   if (!tod->atr) {
      free(tod);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_get_string(heap, params[0], 0, -1, &tod->hostname, NULL);
   if (err) {
      free(tod->atr);
      free(tod);
      return fixscript_error(heap, error, err);
   }

   tod->proc = proc;
   tod->port = fixscript_get_int(params[1]);
   tod->callback = params[2];
   tod->data = params[3];
   async_process_ref(proc);
   fixscript_ref(heap, tod->data);

   if (!async_run_thread(tcp_open_func, tod)) {
      async_process_unref(proc);
      fixscript_unref(heap, tod->data);
      free(tod->hostname);
      free(tod->atr);
      free(tod);
      *error = fixscript_create_error_string(heap, "can't create thread");
      return fixscript_int(0);
   }

   return fixscript_int(0);
}


static Value native_async_tcp_connection_read(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncHandle *handle;
#ifdef _WIN32
   WSABUF wsabuf;
   DWORD flags;
   DWORD read;
#endif

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC, NULL);
   if (!handle || handle->type != ASYNC_TCP_CONNECTION) {
      *error = fixscript_create_error_string(heap, "invalid async stream handle");
      return fixscript_int(0);
   }
   
   if (handle->active & ASYNC_READ) {
      *error = fixscript_create_error_string(heap, "only one read operation can be active at a time");
      return fixscript_int(0);
   }

#ifdef _WIN32
   if (params[3].value > sizeof(handle->read.buf)) {
      params[3].value = sizeof(handle->read.buf);
   }
   if (params[3].value < 0) {
      *error = fixscript_create_error_string(heap, "negative length");
      return fixscript_int(0);
   }
#endif

   handle->active |= ASYNC_READ;
   handle->read.callback = params[4];
   handle->read.data = params[5];
   handle->read.array = params[1];
   handle->read.off = params[2].value;
   handle->read.len = params[3].value;
   fixscript_ref(heap, handle->read.data);
   fixscript_ref(heap, handle->read.array);

#ifdef _WIN32
   memset(&handle->read.overlapped, 0, sizeof(WSAOVERLAPPED));

   wsabuf.len = params[3].value;
   wsabuf.buf = handle->read.buf;
   flags = 0;
   WSARecv(handle->socket, &wsabuf, 1, &read, &flags, &handle->read.overlapped, NULL);
#else
   update_poll_ctl(handle);
#endif

   return fixscript_int(0);
}


#ifdef _WIN32
static Value native_async_tcp_connection_write(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncHandle *handle;
   WSABUF wsabuf;
   DWORD written;
   int err;

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC, NULL);
   if (!handle || handle->type != ASYNC_TCP_CONNECTION) {
      *error = fixscript_create_error_string(heap, "invalid async stream handle");
      return fixscript_int(0);
   }
   
   if (handle->active & ASYNC_WRITE) {
      *error = fixscript_create_error_string(heap, "only one write operation can be active at a time");
      return fixscript_int(0);
   }

   if (params[3].value > sizeof(handle->write.buf)) {
      params[3].value = sizeof(handle->write.buf);
   }

   err = fixscript_get_array_bytes(heap, params[1], params[2].value, params[3].value, handle->write.buf);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   handle->active |= ASYNC_WRITE;
   handle->write.callback = params[4];
   handle->write.data = params[5];
   fixscript_ref(heap, handle->write.data);

   memset(&handle->write.overlapped, 0, sizeof(WSAOVERLAPPED));

   wsabuf.len = params[3].value;
   wsabuf.buf = handle->write.buf;
   WSASend(handle->socket, &wsabuf, 1, &written, 0, &handle->write.overlapped, NULL);

   return fixscript_int(0);
}
#else
static Value native_async_tcp_connection_write(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncHandle *handle;
   char *buf;
   int err;
   int written;

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC, NULL);
   if (!handle || handle->type != ASYNC_TCP_CONNECTION) {
      *error = fixscript_create_error_string(heap, "invalid async stream handle");
      return fixscript_int(0);
   }
   
   if (handle->active & ASYNC_WRITE) {
      *error = fixscript_create_error_string(heap, "only one write operation can be active at a time");
      return fixscript_int(0);
   }

   err = fixscript_lock_array(heap, params[1], params[2].value, params[3].value, (void **)&buf, 1, 1);
   if (err) {
      fixscript_error(heap, error, err);
      return fixscript_int(0);
   }

   handle->active |= ASYNC_WRITE;
   handle->write.callback = params[4];
   handle->write.data = params[5];
   fixscript_ref(heap, handle->write.data);

   written = write(handle->fd, buf, params[3].value);
   if (written < 0 && errno == EAGAIN) {
      written = 0;
   }
   handle->write.result = written;
   update_poll_ctl(handle);

   fixscript_unlock_array(heap, params[1], params[2].value, params[3].value, (void **)&buf, 1, 0);

   return fixscript_int(0);
}
#endif


static Value native_async_tcp_connection_close(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncHandle *handle;

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC, NULL);
   if (!handle || handle->type != ASYNC_TCP_CONNECTION) {
      *error = fixscript_create_error_string(heap, "invalid async stream handle");
      return fixscript_int(0);
   }

#ifdef _WIN32
   closesocket(handle->socket);
   handle->socket = INVALID_SOCKET;
#else
   poll_remove_socket(handle->proc->poll, handle->fd);
   close(handle->fd);
   handle->fd = -1;
#endif
   return fixscript_int(0);
}


static Value native_async_tcp_server_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int local_only = data == (void *)1;
   AsyncProcess *proc;
   AsyncServerHandle *handle;
   struct sockaddr_in server;
#if defined(_WIN32)
   SOCKET sock = INVALID_SOCKET;
#else
   int fd = -1;
#endif
   int reuse;
   Value retval = fixscript_int(0);
   
   proc = get_async_process(heap, error);
   if (!proc) return fixscript_int(0);

#if defined(_WIN32)
   sock = socket(AF_INET, SOCK_STREAM, 0);
   if (sock == INVALID_SOCKET) {
      goto io_error;
   }

   reuse = 1;
   if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
      goto io_error;
   }

   server.sin_family = AF_INET;
   server.sin_addr.s_addr = htonl(local_only? INADDR_LOOPBACK : INADDR_ANY);
   server.sin_port = htons(params[0].value);

   if (bind(sock, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR) {
      goto io_error;
   }

   if (listen(sock, 5) == SOCKET_ERROR) {
      goto io_error;
   }
#else
   fd = socket(AF_INET, SOCK_STREAM, 0);
   if (fd == -1) {
      goto io_error;
   }

   reuse = 1;
   if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
      goto io_error;
   }

   server.sin_family = AF_INET;
   server.sin_addr.s_addr = htonl(local_only? INADDR_LOOPBACK : INADDR_ANY);
   server.sin_port = htons(params[0].value);

   if (bind(fd, (struct sockaddr *)&server, sizeof(server)) < 0) {
      goto io_error;
   }

   if (listen(fd, 5) < 0) {
      goto io_error;
   }
#endif

   handle = calloc(1, sizeof(AsyncServerHandle));
   if (!handle) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   handle->proc = proc;
   async_process_ref(handle->proc);
   handle->type = ASYNC_TCP_SERVER;
#if defined(_WIN32)
   handle->socket = sock;
   handle->accept_socket = INVALID_SOCKET;
   if (!CreateIoCompletionPort((HANDLE)handle->socket, proc->iocp, (ULONG_PTR)handle, 0)) {
      async_process_unref(handle->proc);
      free(handle);
      goto io_error;
   }
#else
   handle->fd = fd;
   if (!poll_add_socket(proc->poll, fd, handle, 0)) {
      async_process_unref(handle->proc);
      free(handle);
      goto io_error;
   }
#endif

   retval = fixscript_create_handle(heap, HANDLE_TYPE_ASYNC, handle, free_async_handle);
#if defined(_WIN32)
   sock = INVALID_SOCKET;
#else
   fd = -1;
#endif
   if (!retval.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

error:
#if defined(_WIN32)
   if (!retval.value && sock != INVALID_SOCKET) {
      closesocket(sock);
   }
#else
   if (!retval.value && fd != -1) {
      close(fd);
   }
#endif
   return retval;

io_error:
   *error = fixscript_create_error_string(heap, "I/O error");
   goto error;

   return fixscript_int(0);
}


static Value native_async_tcp_server_close(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncServerHandle *handle;

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC, NULL);
   if (!handle || handle->type != ASYNC_TCP_SERVER) {
      *error = fixscript_create_error_string(heap, "invalid async TCP server handle");
      return fixscript_int(0);
   }

#ifdef _WIN32
   closesocket(handle->socket);
   handle->socket = INVALID_SOCKET;
#else
   poll_remove_socket(handle->proc->poll, handle->fd);
   close(handle->fd);
   handle->fd = -1;
#endif
   return fixscript_int(0);
}


static Value native_async_tcp_server_accept(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncServerHandle *handle;
#ifdef _WIN32
   BOOL ret;
   int flag;
#endif

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC, NULL);
   if (!handle || handle->type != ASYNC_TCP_SERVER) {
      *error = fixscript_create_error_string(heap, "invalid async TCP server handle");
      return fixscript_int(0);
   }
   
   if (handle->active) {
      *error = fixscript_create_error_string(heap, "only one accept operation can be active at a time");
      return fixscript_int(0);
   }

#ifdef _WIN32
   handle->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (handle->accept_socket == INVALID_SOCKET) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   flag = 1;
   setsockopt(handle->accept_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
#endif

   handle->active = 1;
   handle->callback = params[1];
   handle->data = params[2];
   fixscript_ref(heap, handle->data);

#ifdef _WIN32
   memset(&handle->overlapped, 0, sizeof(OVERLAPPED));
   ret = AcceptEx(handle->socket, handle->accept_socket, handle->buf, 0, sizeof(struct sockaddr_in) + 16, sizeof(struct sockaddr_in) + 16, NULL, &handle->overlapped);
   if (ret != 0 || WSAGetLastError() != ERROR_IO_PENDING) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
#else
   update_poll_ctl((AsyncHandle *)handle);
#endif
   return fixscript_int(0);
}


static uint32_t get_time()
{
#if defined(_WIN32)
   return timeGetTime();
#elif defined(__APPLE__)
   uint64_t time;
   mach_timebase_info_data_t info;
   time = mach_absolute_time();
   mach_timebase_info(&info);
   return time * info.numer / info.denom / 1000000;
#else
   struct timespec ts;
   if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
      return 0;
   }
   return ((uint64_t)ts.tv_sec) * 1000 + ((uint64_t)ts.tv_nsec / 1000000L);
#endif
}


static void wait_events(AsyncProcess *proc, int timeout)
{
   AsyncTimer *timer;
   int32_t diff;
#ifdef _WIN32
   CompletedIO *cio;
#endif

   pthread_mutex_lock(&proc->mutex);
   timer = proc->timers;
   if (timer) {
      if (timer->immediate) {
         timeout = 0;
      }
      else {
         diff = (int32_t)(timer->time - get_time());
         if (timeout < 0 || diff < timeout) {
            timeout = diff;
            if (timeout < 0) timeout = 0;
         }
      }
   }
   pthread_mutex_unlock(&proc->mutex);

#ifdef _WIN32
   if (timeout < 0) {
      timeout = INFINITE;
   }
   proc->num_events = 0;
   cio = &proc->completed_ios[0];

   while (proc->num_events < sizeof(proc->completed_ios)/sizeof(CompletedIO)) {
      GetQueuedCompletionStatus(proc->iocp, &cio->transferred, &cio->key, (OVERLAPPED **)&cio->overlapped, timeout);
      if (!cio->overlapped) break;

      cio = &proc->completed_ios[++proc->num_events];
      timeout = 0;
   }
#else
   if (timeout < 0) {
      timeout = -1;
   }
   poll_wait(proc->poll, timeout);
#endif
}


static int process_events(AsyncProcess *proc, Heap *heap, Value *error)
{
   AsyncThreadResult *atr = NULL, *atr_next;
   AsyncTimer *timer;
   AsyncHandle *handle;
   Value handle_val, callback_error;
   uint32_t time = 0;
   int32_t diff;
   int time_obtained;
#if defined(_WIN32)
   CompletedIO *cio;
   int i;
#else
   int flags;
#endif

#if defined(_WIN32)
   for (i=0; i<proc->num_events; i++) {
      cio = &proc->completed_ios[i];
      if (cio->overlapped == proc) {
         continue;
      }

      handle = (AsyncHandle *)(uintptr_t)cio->key;
      if (handle->type == ASYNC_TCP_CONNECTION) {
         if ((void *)cio->overlapped == &handle->read.overlapped) {
            if (handle->active & ASYNC_READ) {
               Value callback, data;
               int result = cio->transferred;

               callback = handle->read.callback;
               data = handle->read.data;
               handle->active &= ~ASYNC_READ;

               if (fixscript_set_array_bytes(heap, handle->read.array, handle->read.off, result, handle->read.buf) != 0) {
                  result = -1;
               }
               fixscript_unref(heap, handle->read.array);

               fixscript_call(heap, callback, 2, &callback_error, data, fixscript_int(result));
               if (callback_error.value) {
                  fixscript_dump_value(heap, callback_error, 1);
               }
               fixscript_unref(heap, data);
            }
         }
         if ((void *)cio->overlapped == &handle->write.overlapped) {
            if (handle->active & ASYNC_WRITE) {
               Value callback, data;
               int result = cio->transferred;

               callback = handle->write.callback;
               data = handle->write.data;
               handle->active &= ~ASYNC_WRITE;

               fixscript_call(heap, callback, 2, &callback_error, data, fixscript_int(result));
               if (callback_error.value) {
                  fixscript_dump_value(heap, callback_error, 1);
               }
               fixscript_unref(heap, data);
            }
         }
      }
      else if (handle->type == ASYNC_TCP_SERVER) {
         AsyncServerHandle *server_handle = (AsyncServerHandle *)handle;
         if ((void *)cio->overlapped == &server_handle->overlapped) {
            AsyncHandle *new_handle;
            Value callback, data;
            Value result = fixscript_int(0);
            SOCKET socket;

            socket = server_handle->accept_socket;
            server_handle->accept_socket = INVALID_SOCKET;

            new_handle = calloc(1, sizeof(AsyncHandle));
            if (!new_handle) {
               closesocket(socket);
            }
            else {
               new_handle->proc = proc;
               async_process_ref(new_handle->proc);
               new_handle->type = ASYNC_TCP_CONNECTION;
               new_handle->socket = socket;
               if (!CreateIoCompletionPort((HANDLE)new_handle->socket, proc->iocp, (ULONG_PTR)new_handle, 0)) {
                  async_process_unref(new_handle->proc);
                  closesocket(new_handle->socket);
                  free(new_handle);
               }
               else {
                  result = fixscript_create_handle(heap, HANDLE_TYPE_ASYNC, new_handle, free_async_handle);
               }
            }

            callback = server_handle->callback;
            data = server_handle->data;
            server_handle->active = 0;

            fixscript_call(heap, callback, 2, &callback_error, data, result);
            if (callback_error.value) {
               fixscript_dump_value(heap, callback_error, 1);
            }
            fixscript_unref(heap, data);
         }
      }
   }
#else
   for (;;) {
      handle = poll_get_event(proc->poll, &flags);
      if (!handle) break;

      if (handle->type == ASYNC_TCP_CONNECTION) {
         if (flags & ASYNC_READ) {
            if (handle->active & ASYNC_READ) {
               Value callback, data;
               char *buf;
               int err = 0, result = -1;

               callback = handle->read.callback;
               data = handle->read.data;
               handle->active &= ~ASYNC_READ;

               err = fixscript_lock_array(heap, handle->read.array, handle->read.off, handle->read.len, (void **)&buf, 1, 0);
               if (!err) {
                  result = read(handle->fd, buf, handle->read.len);
                  handle->read.len = result;
                  if (handle->read.len < 0) handle->read.len = 0;
                  fixscript_unlock_array(heap, handle->read.array, handle->read.off, handle->read.len, (void **)&buf, 1, 1);
               }
               fixscript_unref(heap, handle->read.array);

               fixscript_call(heap, callback, 2, &callback_error, data, fixscript_int(result));
               if (callback_error.value) {
                  fixscript_dump_value(heap, callback_error, 1);
               }
               fixscript_unref(heap, data);
            }
         }
         if (flags & ASYNC_WRITE) {
            if (handle->active & ASYNC_WRITE) {
               Value callback, data;

               callback = handle->write.callback;
               data = handle->write.data;
               handle->active &= ~ASYNC_WRITE;

               fixscript_call(heap, callback, 2, &callback_error, data, fixscript_int(handle->write.result));
               if (callback_error.value) {
                  fixscript_dump_value(heap, callback_error, 1);
               }
               fixscript_unref(heap, data);
            }
         }
         update_poll_ctl(handle);
      }
      else if (handle->type == ASYNC_TCP_SERVER) {
         AsyncServerHandle *server_handle = (AsyncServerHandle *)handle;
         AsyncHandle *new_handle;
         if (flags & ASYNC_READ) {
            Value callback, data;
            Value result = fixscript_int(0);
            int fd, flag;
            fd = accept(server_handle->fd, NULL, NULL);
            if (fd >= 0) {
               flag = 1;
               setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
               flag = fcntl(fd, F_GETFL);
               flag |= O_NONBLOCK;
               fcntl(fd, F_SETFL, flag);

               new_handle = calloc(1, sizeof(AsyncHandle));
               if (!new_handle) {
                  close(fd);
               }
               else {
                  new_handle->proc = proc;
                  async_process_ref(new_handle->proc);
                  new_handle->type = ASYNC_TCP_CONNECTION;
                  new_handle->fd = fd;
                  if (!poll_add_socket(proc->poll, fd, new_handle, 0)) {
                     async_process_unref(new_handle->proc);
                     close(new_handle->fd);
                     free(new_handle);
                  }
                  else {
                     result = fixscript_create_handle(heap, HANDLE_TYPE_ASYNC, new_handle, free_async_handle);
                  }
               }
            }

            callback = server_handle->callback;
            data = server_handle->data;
            server_handle->active = 0;

            fixscript_call(heap, callback, 2, &callback_error, data, result);
            if (callback_error.value) {
               fixscript_dump_value(heap, callback_error, 1);
            }
            fixscript_unref(heap, data);
         }
         update_poll_ctl(handle);
      }
   }
#endif

   pthread_mutex_lock(&proc->mutex);
   atr = proc->thread_results;
   proc->thread_results = NULL;
   pthread_mutex_unlock(&proc->mutex);

   while (atr) {
      if (atr->type == ASYNC_TCP_CONNECTION) {
         #ifdef _WIN32
         if (atr->socket != INVALID_SOCKET)
         #else
         if (atr->fd != -1)
         #endif
         {
            handle = calloc(1, sizeof(AsyncHandle));
            if (!handle) {
               #ifdef _WIN32
               closesocket(handle->socket);
               #else
               close(handle->fd);
               #endif
               fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
               goto error;
            }
            handle->proc = proc;
            async_process_ref(handle->proc);
            handle->type = ASYNC_TCP_CONNECTION;
            #ifdef _WIN32
            handle->socket = atr->socket;
            if (!CreateIoCompletionPort((HANDLE)handle->socket, proc->iocp, (ULONG_PTR)handle, 0)) {
               async_process_unref(handle->proc);
               closesocket(handle->socket);
               free(handle);
               *error = fixscript_create_error_string(heap, "can't add socket to IO completion port");
               goto error;
            }
            #else
            handle->fd = atr->fd;
            if (!poll_add_socket(proc->poll, atr->fd, handle, 0)) {
               async_process_unref(handle->proc);
               close(handle->fd);
               free(handle);
               *error = fixscript_create_error_string(heap, "can't add socket to poll");
               goto error;
            }
            #endif

            handle_val = fixscript_create_handle(heap, HANDLE_TYPE_ASYNC, handle, free_async_handle);
            if (!handle_val.value) {
               fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
               goto error;
            }
         }
         else {
            handle_val = fixscript_int(0);
         }

         fixscript_call(heap, atr->callback, 2, &callback_error, atr->data, handle_val);
         if (callback_error.value) {
            fixscript_dump_value(heap, callback_error, 1);
         }
         fixscript_unref(heap, atr->data);
      }
      atr_next = atr->next;
      free(atr);
      atr = atr_next;
   }

   pthread_mutex_lock(&proc->mutex);
   time_obtained = 0;
   while (proc->timers) {
      timer = proc->timers;
      if (!timer->immediate) {
         if (!time_obtained) {
            time = get_time();
            time_obtained = 1;
         }
         diff = (int32_t)(timer->time - time);
         if (diff > 0) break;
      }

      proc->timers = timer->next;
      pthread_mutex_unlock(&proc->mutex);

      fixscript_call(heap, timer->callback, 1, &callback_error, timer->data);
      if (callback_error.value) {
         fixscript_dump_value(heap, callback_error, 1);
      }
      fixscript_unref(heap, timer->data);

      free(timer);

      pthread_mutex_lock(&proc->mutex);
   }
   pthread_mutex_unlock(&proc->mutex);
   return 1;

error:
   while (atr) {
      atr_next = atr->next;
      free(atr);
      atr = atr_next;
   }
   return 0;
}


static Value native_async_process(Heap *heap, Value *error, int num_params, Value *params, void *func_data)
{
   AsyncProcess *proc;
   int timeout = -1, infinite;
   Value quit_value;
   
   proc = get_async_process(heap, error);
   if (!proc) return fixscript_int(0);
   
   if (proc->foreign_notify_func) {
      *error = fixscript_create_error_string(heap, "can't use async_process when integrated with other event loop");
      return fixscript_int(0);
   }

   if (num_params == 1) {
      timeout = fixscript_get_int(params[0]);
   }

   fixscript_unref(heap, proc->quit_value);
   infinite = (timeout < 0);
   proc->quit = !infinite;
   proc->quit_value = fixscript_int(0);

   if (infinite) {
      timeout = -1;
   }

   do {
      wait_events(proc, timeout);
      if (!process_events(proc, heap, error)) {
         return fixscript_int(0);
      }
   }
   while (!proc->quit);

   quit_value = proc->quit_value;
   proc->quit_value = fixscript_int(0);
   fixscript_unref(heap, quit_value);
   return quit_value;
}


static Value native_async_run_later(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncProcess *proc;
   AsyncTimer *timer, *t, **p;
   int32_t diff;

   proc = get_async_process(heap, error);
   if (!proc) return fixscript_int(0);

   timer = calloc(1, sizeof(AsyncTimer));
   if (!timer) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   if (params[0].value == 0) {
      timer->immediate = 1;
   }
   else {
      timer->time = get_time() + (uint32_t)params[0].value;
   }

   timer->callback = params[1];
   timer->data = params[2];
   fixscript_ref(heap, timer->data);

   pthread_mutex_lock(&proc->mutex);
   if (timer->immediate || !proc->timers) {
      timer->next = proc->timers;
      proc->timers = timer;
   }
   else {
      p = &proc->timers;
      for (t=proc->timers; t; t=t->next) {
         diff = (int32_t)(timer->time - t->time);
         if (!t->immediate && diff <= 0) {
            timer->next = t;
            *p = timer;
            break;
         }
         if (!t->next) {
            t->next = timer;
            break;
         }
         p = &t->next;
      }
   }
   pthread_mutex_unlock(&proc->mutex);

   if (proc->foreign_notify_func) {
      async_process_notify(proc);
   }
   return fixscript_int(0);
}


static Value native_async_quit(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncProcess *proc;
   Value quit_value = fixscript_int(0);
   
   proc = get_async_process(heap, error);
   if (!proc) return fixscript_int(0);
   
   if (proc->foreign_notify_func) {
      *error = fixscript_create_error_string(heap, "can't use async_quit when integrated with other event loop");
      return fixscript_int(0);
   }

   if (num_params == 1) {
      quit_value = params[0];
   }

   fixscript_unref(heap, proc->quit_value);
   proc->quit = 1;
   proc->quit_value = quit_value;
   fixscript_ref(heap, proc->quit_value);

   return fixscript_int(0);
}


static void *process_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   ProcessHandle *handle = p1;
   switch (op) {
      case HANDLE_OP_FREE:
         if (__sync_sub_and_fetch(&handle->refcnt, 1) == 0) {
            #ifdef _WIN32
               if (handle->flags & REDIR_IN) CloseHandle(handle->in);
               if (handle->flags & REDIR_OUT) CloseHandle(handle->out);
               if (handle->flags & REDIR_ERR) CloseHandle(handle->err);
               if (handle->process != ERROR_INVALID_HANDLE) {
                  CloseHandle(handle->process);
               }
            #else
               if (handle->flags & REDIR_IN) close(handle->in_fd);
               if (handle->flags & REDIR_OUT) close(handle->out_fd);
               if (handle->flags & REDIR_ERR) close(handle->err_fd);
            #endif
            free(handle);
         }
         break;

      case HANDLE_OP_COPY:
         __sync_add_and_fetch(&handle->refcnt, 1);
         return handle;
   }
   return NULL;
}


static Value native_process_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#ifdef _WIN32
   ProcessHandle *handle;
   Value *arg_values = NULL, key_val, value_val, handle_val, ret = fixscript_int(0);
   uint16_t **args = NULL, *p, *cmdline = NULL, **envs = NULL, *key, *value, *envblock = NULL, *path = NULL;
   DWORD proc_flags = 0;
   STARTUPINFO si;
   PROCESS_INFORMATION pi;
   SECURITY_ATTRIBUTES sa;
   HANDLE pipes[6];
   HANDLE in_other, out_other, err_other;
   int flags = params[3].value;
   int num_args=0, total_len, len, num_envs=0, num_pipes;
   int i, j, err, needs_quotes, pos, idx;

   if (flags & REDIR_MERGE_ERR) {
      if ((flags & (REDIR_OUT | REDIR_ERR)) != REDIR_OUT) {
         *error = fixscript_create_error_string(heap, "incompatible flags");
         goto error;
      }
   }

   err = fixscript_get_array_length(heap, params[0], &num_args);
   if (!err) {
      if (num_args < 1) {
         *error = fixscript_create_error_string(heap, "must provide executable argument");
         goto error;
      }
      arg_values = calloc(num_args, sizeof(Value));
      if (!arg_values) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_get_array_range(heap, params[0], 0, num_args, arg_values);
   }
   if (!err) {
      args = calloc(num_args, sizeof(uint8_t *));
      if (!args) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      for (i=0; i<num_args; i++) {
         err = fixscript_get_string_utf16(heap, arg_values[i], 0, -1, &args[i], NULL);
         if (err) break;
      }
   }
   if (!err) {
      total_len = 0;
      for (i=0; i<num_args; i++) {
         if (i > 0) total_len++;
         needs_quotes = 0;
         for (p = args[i]; *p; p++) {
            if (*p == ' ' || *p == '"') {
               needs_quotes = 1;
               break;
            }
         }
         if (needs_quotes) {
            total_len += 2;
         }
         for (p = args[i]; *p; p++) {
            if (*p == '"') {
               total_len += 3;
               continue;
            }
            total_len++;
         }
      }
      cmdline = calloc(total_len+1, sizeof(uint16_t));
      if (!cmdline) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      for (i=0, j=0; i<num_args; i++) {
         if (i > 0) {
            cmdline[j++] = ' ';
         }
         needs_quotes = 0;
         for (p = args[i]; *p; p++) {
            if (*p == ' ' || *p == '"') {
               needs_quotes = 1;
               break;
            }
         }
         if (needs_quotes) {
            cmdline[j++] = '"';
         }
         for (p = args[i]; *p; p++) {
            if (*p == '"') {
               cmdline[j++] = '"';
               cmdline[j++] = '"';
               cmdline[j++] = '"';
               continue;
            }
            cmdline[j++] = *p;
         }
         if (needs_quotes) {
            cmdline[j++] = '"';
         }
      }
      cmdline[j] = 0;
      if (j != total_len) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (params[1].value) {
      if (!err) {
         err = fixscript_get_array_length(heap, params[1], &num_envs);
      }
      if (!err) {
         envs = calloc(num_envs*2, sizeof(uint16_t *));
         if (!envs) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
      if (!err) {
         pos = 0;
         idx = 0;
         while (fixscript_iter_hash(heap, params[1], &key_val, &value_val, &pos)) {
            err = fixscript_get_string_utf16(heap, key_val, 0, -1, &key, NULL);
            if (err) {
               break;
            }
            err = fixscript_get_string_utf16(heap, value_val, 0, -1, &value, NULL);
            if (err) {
               free(key);
               break;
            }
            envs[idx++] = key;
            envs[idx++] = value;
         }
         if (!err && idx != num_envs*2) {
            err = FIXSCRIPT_ERR_INVALID_ACCESS;
         }
      }
      if (!err) {
         total_len = 0;
         for (i=0; i<num_envs; i++) {
            total_len += wcslen(envs[i*2+0]);
            total_len++;
            total_len += wcslen(envs[i*2+1]);
            total_len++;
         }
         envblock = calloc(total_len+1, sizeof(uint16_t));
         if (!envblock) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
      if (!err) {
         for (i=0, j=0; i<num_envs; i++) {
            len = wcslen(envs[i*2+0]);
            memcpy(envblock+j, envs[i*2+0], len*2);
            j += len;
            envblock[j++] = '=';
            
            len = wcslen(envs[i*2+1]);
            memcpy(envblock+j, envs[i*2+1], len*2);
            j += len;
            envblock[j++] = 0;
         }
         envblock[j] = 0;
         if (j != total_len) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
      proc_flags |= CREATE_UNICODE_ENVIRONMENT;
   }
   if (params[2].value) {
      if (!err) {
         err = fixscript_get_string_utf16(heap, params[2], 0, -1, &path, NULL);
      }
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   handle = calloc(1, sizeof(ProcessHandle));
   if (!handle) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   memset(&si, 0, sizeof(STARTUPINFO));
   si.cb = sizeof(STARTUPINFO);
   memset(&pi, 0, sizeof(PROCESS_INFORMATION));

   if (flags & (REDIR_IN | REDIR_OUT | REDIR_ERR)) {
      si.dwFlags |= STARTF_USESTDHANDLES;

      memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
      sa.nLength = sizeof(SECURITY_ATTRIBUTES);
      sa.bInheritHandle = 1;

      num_pipes = 0;
      if (flags & REDIR_IN) num_pipes++;
      if (flags & REDIR_OUT) num_pipes++;
      if (flags & REDIR_ERR) num_pipes++;
      for (i=0; i<num_pipes; i++) {
         if (!CreatePipe(&pipes[i*2+0], &pipes[i*2+1], &sa, 0)) {
            while (i > 0) {
               i--;
               CloseHandle(pipes[i*2+0]);
               CloseHandle(pipes[i*2+1]);
            }
            *error = fixscript_create_error_string(heap, "can't create pipe");
            free(handle);
            goto error;
         }
      }

      if (flags & REDIR_IN) {
         num_pipes--;
         handle->in = pipes[num_pipes*2+1];
         in_other = pipes[num_pipes*2+0];
         si.hStdInput = pipes[num_pipes*2+0];
         SetHandleInformation(handle->in, HANDLE_FLAG_INHERIT, 0);
      }
      else {
         si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
      }

      if (flags & REDIR_OUT) {
         num_pipes--;
         handle->out = pipes[num_pipes*2+0];
         out_other = pipes[num_pipes*2+1];
         si.hStdOutput = pipes[num_pipes*2+1];
         if (flags & REDIR_MERGE_ERR) {
            si.hStdError = pipes[num_pipes*2+1];
         }
         SetHandleInformation(handle->out, HANDLE_FLAG_INHERIT, 0);
      }
      else {
         si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
      }

      if (flags & REDIR_ERR) {
         num_pipes--;
         handle->err = pipes[num_pipes*2+0];
         err_other = pipes[num_pipes*2+1];
         si.hStdError = pipes[num_pipes*2+1];
         SetHandleInformation(handle->err, HANDLE_FLAG_INHERIT, 0);
      }
      else {
         si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
      }
   }

   handle->refcnt = 1;
   handle->flags = flags;
   handle->process = ERROR_INVALID_HANDLE;

   handle_val = fixscript_create_value_handle(heap, HANDLE_TYPE_PROCESS, handle, process_handle_func);
   if (!handle_val.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   if (!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, proc_flags, envblock, path, &si, &pi)) {
      *error = fixscript_create_error_string(heap, "can't start process");
      goto error;
   }
   
   if (flags & REDIR_IN) {
      CloseHandle(in_other);
   }
   if (flags & REDIR_OUT) {
      CloseHandle(out_other);
   }
   if (flags & REDIR_ERR) {
      CloseHandle(err_other);
   }

   CloseHandle(pi.hThread);
   handle->process = pi.hProcess;

   ret = handle_val;

error:
   free(arg_values);
   if (args) {
      for (i=0; i<num_args; i++) {
         free(args[i]);
      }
      free(args);
   }
   free(cmdline);
   if (envs) {
      for (i=0; i<num_envs*2; i++) {
         free(envs[i]);
      }
      free(envs);
   }
   free(envblock);
   free(path);
   return ret;
#else
   ProcessHandle *handle = NULL;
   Value *arg_values = NULL;
   Value key_val, value_val, handle_val, ret = fixscript_int(0);
   int flags = params[3].value;
   char **args = NULL, **envs = NULL, *path = NULL, *key, *value;
   int num_args=0, num_envs=0;
   int i, err, pos, idx, own_handle=1;
   int in_fds[2], out_fds[2], err_fds[2];

   in_fds[0] = -1;
   in_fds[1] = -1;
   out_fds[0] = -1;
   out_fds[1] = -1;
   err_fds[0] = -1;
   err_fds[1] = -1;

   if (flags & REDIR_MERGE_ERR) {
      if ((flags & (REDIR_OUT | REDIR_ERR)) != REDIR_OUT) {
         *error = fixscript_create_error_string(heap, "incompatible flags");
         goto error;
      }
   }

   err = fixscript_get_array_length(heap, params[0], &num_args);
   if (!err) {
      if (num_args < 1) {
         *error = fixscript_create_error_string(heap, "must provide executable argument");
         goto error;
      }
      arg_values = calloc(num_args, sizeof(Value));
      if (!arg_values) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_get_array_range(heap, params[0], 0, num_args, arg_values);
   }
   if (!err) {
      args = calloc(num_args+1, sizeof(char *));
      if (!args) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      for (i=0; i<num_args; i++) {
         err = fixscript_get_string(heap, arg_values[i], 0, -1, &args[i], NULL);
         if (err) break;
      }
   }
   if (params[1].value) {
      if (!err) {
         err = fixscript_get_array_length(heap, params[1], &num_envs);
      }
      if (!err) {
         envs = calloc(num_envs*2, sizeof(char *));
         if (!envs) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
      if (!err) {
         pos = 0;
         idx = 0;
         while (fixscript_iter_hash(heap, params[1], &key_val, &value_val, &pos)) {
            err = fixscript_get_string(heap, key_val, 0, -1, &key, NULL);
            if (err) {
               break;
            }
            err = fixscript_get_string(heap, value_val, 0, -1, &value, NULL);
            if (err) {
               free(key);
               break;
            }
            envs[idx++] = key;
            envs[idx++] = value;
         }
         if (!err && idx != num_envs*2) {
            err = FIXSCRIPT_ERR_INVALID_ACCESS;
         }
      }
   }
   if (params[2].value) {
      if (!err) {
         err = fixscript_get_string(heap, params[2], 0, -1, &path, NULL);
      }
   }
   if (!err) {
      handle = calloc(1, sizeof(ProcessHandle));
      if (!handle) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   handle->refcnt = 1;
   handle->flags = flags;
   handle->ret_value = -1;
   handle->in_fd = -1;
   handle->out_fd = -1;
   handle->err_fd = -1;

   if (flags & REDIR_IN) {
      if (pipe(in_fds) < 0) {
         *error = fixscript_create_error_string(heap, "can't create pipe");
         goto error;
      }
   }

   if (flags & REDIR_OUT) {
      if (pipe(out_fds) < 0) {
         *error = fixscript_create_error_string(heap, "can't create pipe");
         goto error;
      }
   }

   if (flags & REDIR_ERR) {
      if (pipe(err_fds) < 0) {
         *error = fixscript_create_error_string(heap, "can't create pipe");
         goto error;
      }
   }

   handle->pid = fork();

   if (handle->pid < 0) {
      *error = fixscript_create_error_string(heap, "can't fork process");
      goto error;
   }

   handle_val = fixscript_create_value_handle(heap, HANDLE_TYPE_PROCESS, handle, process_handle_func);
   own_handle = 0;
   if (!handle_val.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   if (handle->pid == 0) {
      if (flags & REDIR_IN) {
         dup2(in_fds[0], 0);
         close(in_fds[0]);
         close(in_fds[1]);
      }
      if (flags & REDIR_OUT) {
         dup2(out_fds[1], 1);
         if (flags & REDIR_MERGE_ERR) {
            dup2(out_fds[1], 2);
         }
         close(out_fds[0]);
         close(out_fds[1]);
      }
      if (flags & REDIR_ERR) {
         dup2(err_fds[1], 2);
         close(err_fds[0]);
         close(err_fds[1]);
      }
      if (path) {
         chdir(path);
      }
      if (envs) {
#ifdef __linux__
         clearenv();
#else
         environ = NULL;
#endif
         for (i=0; i<num_envs; i++) {
            setenv(envs[i*2+0], envs[i*2+1], 0);
         }
      }
      execvp(args[0], args);
      exit(1);
   }

   if (flags & REDIR_IN) {
      handle->in_fd = in_fds[1];
      close(in_fds[0]);
      in_fds[0] = -1;
      in_fds[1] = -1;
   }

   if (flags & REDIR_OUT) {
      handle->out_fd = out_fds[0];
      close(out_fds[1]);
      out_fds[0] = -1;
      out_fds[1] = -1;
   }

   if (flags & REDIR_ERR) {
      handle->err_fd = err_fds[0];
      close(err_fds[1]);
      err_fds[0] = -1;
      err_fds[1] = -1;
   }

   ret = handle_val;

error:
   free(arg_values);
   if (args) {
      for (i=0; i<num_args; i++) {
         free(args[i]);
      }
      free(args);
   }
   if (envs) {
      for (i=0; i<num_envs*2; i++) {
         free(envs[i]);
      }
      free(envs);
   }
   free(path);
   if (own_handle) {
      free(handle);
   }
   if (in_fds[0] != -1) close(in_fds[0]);
   if (in_fds[1] != -1) close(in_fds[1]);
   if (out_fds[0] != -1) close(out_fds[0]);
   if (out_fds[1] != -1) close(out_fds[1]);
   if (err_fds[0] != -1) close(err_fds[0]);
   if (err_fds[1] != -1) close(err_fds[1]);
   return ret;
#endif
}


static Value native_process_read(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ProcessHandle *handle = NULL;
   char *buf;
   int timeout;
   int err;
#ifdef _WIN32
   DWORD ret;
   HANDLE h;
#else
   int ret;
   int fd = (int)(intptr_t)data;
#endif

   if (num_params == 5) {
      handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_PROCESS, NULL);
      if (!handle) {
         *error = fixscript_create_error_string(heap, "invalid process handle");
         return fixscript_int(0);
      }
      params++;

#ifdef _WIN32
      switch ((int)(intptr_t)data) {
         case 1: h = (handle->flags & REDIR_OUT)? handle->out : INVALID_HANDLE_VALUE; break;
         case 2: h = (handle->flags & REDIR_ERR)? handle->err : INVALID_HANDLE_VALUE; break;
         default: h = INVALID_HANDLE_VALUE;
      }

      if (h == INVALID_HANDLE_VALUE) {
         *error = fixscript_create_error_string(heap, "stream is not redirected");
         return fixscript_int(0);
      }
#else
      switch (fd) {
         case 1: fd = (handle->flags & REDIR_OUT)? handle->out_fd : -1; break;
         case 2: fd = (handle->flags & REDIR_ERR)? handle->err_fd : -1; break;
         default: fd = -1;
      }

      if (fd == -1) {
         *error = fixscript_create_error_string(heap, "stream is not redirected");
         return fixscript_int(0);
      }
#endif
   }
   else {
#ifdef _WIN32
      switch ((int)(intptr_t)data) {
         case 0: h = GetStdHandle(STD_INPUT_HANDLE); break;
         default: h = INVALID_HANDLE_VALUE;
      }
#endif
   }

   timeout = params[3].value;

   err = fixscript_lock_array(heap, params[0], params[1].value, params[2].value, (void **)&buf, 1, 0);
   if (err) {
      return fixscript_error(heap, error, err);
   }

#ifdef _WIN32
   if (!ReadFile(h, buf, params[2].value, &ret, NULL)) {
      ret = -1;
   }
#else
   ret = read(fd, buf, params[2].value);
   if (ret < params[2].value) {
      params[2].value = ret >= 0? ret : 0;
   }
#endif

   fixscript_unlock_array(heap, params[0], params[1].value, params[2].value, (void **)&buf, 1, 1);
   if (ret < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   if (ret == 0) ret = -1;
   return fixscript_int(ret);
}


static Value native_process_write(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ProcessHandle *handle = NULL;
   char *buf;
   int timeout;
   int err;
#ifdef _WIN32
   DWORD ret;
   HANDLE h;
#else
   int ret;
   int fd = (int)(intptr_t)data;
#endif

   if (num_params == 5) {
      handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_PROCESS, NULL);
      if (!handle) {
         *error = fixscript_create_error_string(heap, "invalid process handle");
         return fixscript_int(0);
      }
      params++;

#ifdef _WIN32
      switch ((int)(intptr_t)data) {
         case 0: h = (handle->flags & REDIR_IN)? handle->in : INVALID_HANDLE_VALUE; break;
         default: h = INVALID_HANDLE_VALUE;
      }

      if (h == INVALID_HANDLE_VALUE) {
         *error = fixscript_create_error_string(heap, "stream is not redirected");
         return fixscript_int(0);
      }
#else
      switch (fd) {
         case 0: fd = (handle->flags & REDIR_IN)? handle->in_fd : -1; break;
         default: fd = -1;
      }

      if (fd == -1) {
         *error = fixscript_create_error_string(heap, "stream is not redirected");
         return fixscript_int(0);
      }
#endif
   }
   else {
#ifdef _WIN32
      switch ((int)(intptr_t)data) {
         case 1: h = GetStdHandle(STD_OUTPUT_HANDLE); break;
         case 2: h = GetStdHandle(STD_ERROR_HANDLE); break;
         default: h = INVALID_HANDLE_VALUE;
      }
#endif
   }

   timeout = params[3].value;

   err = fixscript_lock_array(heap, params[0], params[1].value, params[2].value, (void **)&buf, 1, 1);
   if (err) {
      return fixscript_error(heap, error, err);
   }

#ifdef _WIN32
   if (!WriteFile(h, buf, params[2].value, &ret, NULL)) {
      ret = -1;
   }
#else
   ret = write(fd, buf, params[2].value);
#endif

   fixscript_unlock_array(heap, params[0], params[1].value, params[2].value, (void **)&buf, 1, 0);

   if (ret < 0) {
      *error = fixscript_create_error_string(heap, "I/O error");
      return fixscript_int(0);
   }
   return fixscript_int(ret);
}


static Value native_process_wait(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ProcessHandle *handle;
#ifdef _WIN32
   DWORD result = 0xFFFFFFFF;
#else
   int status;
#endif

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_PROCESS, NULL);
   if (!handle) {
      *error = fixscript_create_error_string(heap, "invalid process handle");
      return fixscript_int(0);
   }

#ifdef _WIN32
   WaitForSingleObject(handle->process, INFINITE);
   GetExitCodeProcess(handle->process, &result);
   return fixscript_int(result);
#else
   if (handle->pid == 0) {
      return fixscript_int(handle->ret_value);
   }

   if (waitpid(handle->pid, &status, 0) == handle->pid) {
      handle->pid = 0;
      if (WIFEXITED(status)) {
         handle->ret_value = WEXITSTATUS(status);
      }
      return fixscript_int(handle->ret_value);
   }

   handle->pid = 0;
   *error = fixscript_create_error_string(heap, "I/O error");
   return fixscript_int(0);
#endif
}


static Value native_process_kill(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ProcessHandle *handle;
   int force = 0;

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_PROCESS, NULL);
   if (!handle) {
      *error = fixscript_create_error_string(heap, "invalid process handle");
      return fixscript_int(0);
   }

#ifdef _WIN32
   return fixscript_int(0);
#else
   if (handle->pid == 0) {
      return fixscript_int(0);
   }

   if (num_params == 2) {
      force = params[1].value;
   }

   kill(handle->pid, force? SIGKILL : SIGTERM);
   return fixscript_int(0);
#endif
}


static Value native_process_is_running(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ProcessHandle *handle;
#ifdef _WIN32
   DWORD result;
#else
   pid_t ret;
   int status;
#endif

   handle = fixscript_get_handle(heap, params[0], HANDLE_TYPE_PROCESS, NULL);
   if (!handle) {
      *error = fixscript_create_error_string(heap, "invalid process handle");
      return fixscript_int(0);
   }

#ifdef _WIN32
   if (GetExitCodeProcess(handle->process, &result) && result == STILL_ACTIVE) {
      return fixscript_int(1);
   }
   return fixscript_int(0);
#else
   if (handle->pid == 0) {
      return fixscript_int(0);
   }

   ret = waitpid(handle->pid, &status, WNOHANG);
   if (ret == 0) {
      return fixscript_int(1);
   }
   if (ret == handle->pid) {
      handle->pid = 0;
      if (WIFEXITED(status)) {
         handle->ret_value = WEXITSTATUS(status);
      }
      return fixscript_int(0);
   }

   handle->pid = 0;
   *error = fixscript_create_error_string(heap, "I/O error");
   return fixscript_int(0);
#endif
}


static Value native_process_get_current_environment(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#ifdef _WIN32
   Value map, key_val, value_val;
   uint16_t *env, *p, *key, *value;
   int err;

   map = fixscript_create_hash(heap);
   if (!map.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   env = GetEnvironmentStrings();
   for (p=env; *p; p++) {
      key = p;
      value = NULL;
      for (; *p; p++) {
         if (*p == '=') {
            value = p+1;
            break;
         }
      }
      while (*p) {
         p++;
      }
      if (value) {
         key_val = fixscript_create_string_utf16(heap, key, value - key - 1);
         value_val = fixscript_create_string_utf16(heap, value, -1);
      }
      else {
         key_val = fixscript_create_string_utf16(heap, key, -1);
         value_val = fixscript_create_string_utf16(heap, L"", -1);
      }
      if (!key_val.value || !value_val.value) {
         FreeEnvironmentStrings(env);
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
      err = fixscript_set_hash_elem(heap, map, key_val, value_val);
      if (err) {
         FreeEnvironmentStrings(env);
         return fixscript_error(heap, error, err);
      }
   }
   FreeEnvironmentStrings(env);
   return map;
#else
   Value map, key_val, value_val;
   const char **p, *s, *key, *value;
   int err;

   map = fixscript_create_hash(heap);
   if (!map.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   for (p=environ; *p; p++) {
      s = *p;
      key = s;
      value = NULL;
      for (; *s; s++) {
         if (*s == '=') {
            value = s+1;
            break;
         }
      }
      if (value) {
         key_val = fixscript_create_string(heap, key, value - key - 1);
         value_val = fixscript_create_string(heap, value, -1);
      }
      else {
         key_val = fixscript_create_string(heap, key, -1);
         value_val = fixscript_create_string(heap, "", -1);
      }
      if (!key_val.value || !value_val.value) {
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
      err = fixscript_set_hash_elem(heap, map, key_val, value_val);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }
   return map;
#endif
}


static Value native_clock_get_time(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int micro = ((intptr_t)data == 1);
   union {
      int64_t time;
      int32_t i[2];
   } u;

#ifdef _WIN32
   FILETIME ft;

   GetSystemTimeAsFileTime(&ft);
   u.i[0] = ft.dwLowDateTime;
   u.i[1] = ft.dwHighDateTime;
   u.time = ((u.time + 5) / 10LL) - 11644473600000000LL;
#else
   struct timeval tv;

   if (gettimeofday(&tv, NULL) != 0) {
      tv.tv_sec = 0;
      tv.tv_usec = 0;
   }

   if (sizeof(time_t) == 4) {
      // treat time_t as unsigned to give chance for possible unsigned hack once year 2038 happens:
      u.time = ((uint32_t)tv.tv_sec) * 1000000LL + tv.tv_usec;
   }
   else {
      u.time = tv.tv_sec * 1000000LL + tv.tv_usec;
   }
#endif

   if (!micro) {
      u.time = (u.time + 500) / 1000;
   }

   *error = fixscript_int(u.i[1]);
   return fixscript_int(u.i[0]);
}


static Value native_monotonic_get_time(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int micro = ((intptr_t)data == 1);
   uint64_t time;

#if defined(_WIN32)
   uint64_t freq, cnt;
   QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
   QueryPerformanceCounter((LARGE_INTEGER *)&cnt);
   time = cnt * 1000000 / freq;
#elif defined(__linux__)
   struct timespec ts;
   
   if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
      ts.tv_sec = 0;
      ts.tv_nsec = 0;
   }

   time = ts.tv_sec * 1000000LL + (ts.tv_nsec + 500) / 1000;
#else
   struct timeval tv;

   if (gettimeofday(&tv, NULL) != 0) {
      tv.tv_sec = 0;
      tv.tv_usec = 0;
   }

   time = tv.tv_sec * 1000000LL + tv.tv_usec;
#endif

   if (micro) {
      return fixscript_int(time);
   }
   else {
      return fixscript_int((time + 500) / 1000);
   }
}


static Value native_array_create_view(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   SharedArrayHandle *sah;
   Value value;
   char *ptr;
   int arr_len, arr_elem_size;
   int off, len, elem_size;
   int created;
   
   sah = fixscript_get_shared_array_handle(heap, params[0], -1, NULL);
   if (!sah) {
      *error = fixscript_create_error_string(heap, "invalid shared array");
      return fixscript_int(0);
   }

   ptr = fixscript_get_shared_array_handle_data(sah, &arr_len, &arr_elem_size, NULL, -1, NULL);

   off = params[1].value;
   len = params[2].value;
   elem_size = num_params == 4? params[3].value : arr_elem_size;

   if (off < 0 || off >= arr_len || len < 0 || (int64_t)off + (int64_t)len > arr_len) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_BOUNDS);
   }

   ptr += (intptr_t)off * arr_elem_size;
   if ((((intptr_t)ptr) & (elem_size-1)) != 0) {
      *error = fixscript_create_error_string(heap, "unaligned offset");
      return fixscript_int(0);
   }

   value = fixscript_get_shared_array(heap, -1, ptr, len, elem_size, sah);
   if (value.value) {
      return value;
   }

   fixscript_ref_shared_array(sah);
   value = fixscript_create_or_get_shared_array(heap, -1, ptr, (intptr_t)len * arr_elem_size / elem_size, elem_size, (HandleFreeFunc)fixscript_unref_shared_array, sah, &created);
   if (!value.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   if (!created) {
      fixscript_unref_shared_array(sah);
   }
   return value;
}


void fixio_register_functions(Heap *heap)
{
   fixscript_register_handle_types(&handles_offset, NUM_HANDLE_TYPES);
   fixscript_register_heap_key(&async_process_key);

   if (__sync_val_compare_and_swap(&global_initialized, 0, 1) == 0) {
#if defined(_WIN32)
      WSADATA wsa_data;

      WSAStartup(MAKEWORD(2,2), &wsa_data);
#else
      signal(SIGPIPE, SIG_IGN);
#endif
   }
   
   fixscript_register_native_func(heap, "zcompress#1", native_zcompress_memory, (void *)(ZC_COMPRESS));
   fixscript_register_native_func(heap, "zcompress#3", native_zcompress_memory, (void *)(ZC_COMPRESS));
   fixscript_register_native_func(heap, "zuncompress#1", native_zcompress_memory, (void *)(0));
   fixscript_register_native_func(heap, "zuncompress#3", native_zcompress_memory, (void *)(0));
   fixscript_register_native_func(heap, "gzip_compress#1", native_zcompress_memory, (void *)(ZC_COMPRESS | ZC_GZIP));
   fixscript_register_native_func(heap, "gzip_compress#3", native_zcompress_memory, (void *)(ZC_COMPRESS | ZC_GZIP));
   fixscript_register_native_func(heap, "gzip_uncompress#1", native_zcompress_memory, (void *)(ZC_GZIP));
   fixscript_register_native_func(heap, "gzip_uncompress#3", native_zcompress_memory, (void *)(ZC_GZIP));

   fixscript_register_native_func(heap, "zcompress_create#1", native_zcompress_create, (void *)(ZC_COMPRESS));
   fixscript_register_native_func(heap, "zuncompress_create#0", native_zcompress_create, (void *)(0));
   fixscript_register_native_func(heap, "gzip_compress_create#1", native_zcompress_create, (void *)(ZC_COMPRESS | ZC_GZIP));
   fixscript_register_native_func(heap, "gzip_uncompress_create#0", native_zcompress_create, (void *)(ZC_GZIP));
   fixscript_register_native_func(heap, "zcompress_process#8", native_zcompress_process, NULL);
   fixscript_register_native_func(heap, "zcompress_get_read#1", native_zcompress_get_info, (void *)0);
   fixscript_register_native_func(heap, "zcompress_get_written#1", native_zcompress_get_info, (void *)1);

   fixscript_register_native_func(heap, "path_get_separator#0", native_path_get_separator, NULL);
   fixscript_register_native_func(heap, "path_get_prefix_length#1", native_path_get_prefix_length, NULL);
   fixscript_register_native_func(heap, "path_is_valid_name#1", native_path_is_valid_name, NULL);
   fixscript_register_native_func(heap, "path_get_current#0", native_path_get_current, NULL);
   fixscript_register_native_func(heap, "path_get_roots#0", native_path_get_roots, NULL);
   fixscript_register_native_func(heap, "path_get_files#1", native_path_get_files, NULL);
   fixscript_register_native_func(heap, "path_exists#1", native_path_exists, NULL);
   fixscript_register_native_func(heap, "path_get_type#1", native_path_get_type, NULL);
   fixscript_register_native_func(heap, "path_get_length#1", native_path_get_length, NULL);
   fixscript_register_native_func(heap, "path_get_modification_time#1", native_path_get_modification_time, NULL);
   fixscript_register_native_func(heap, "path_get_symlink#1", native_path_get_symlink, NULL);
   fixscript_register_native_func(heap, "path_create_directory#1", native_path_create_directory, NULL);
   fixscript_register_native_func(heap, "path_delete_file#1", native_path_delete_file, NULL);
   fixscript_register_native_func(heap, "path_delete_directory#1", native_path_delete_directory, NULL);

   fixscript_register_native_func(heap, "file_open#2", native_file_open, NULL);
   fixscript_register_native_func(heap, "file_close#1", native_file_close, NULL);
   fixscript_register_native_func(heap, "file_read#4", native_file_read, NULL);
   fixscript_register_native_func(heap, "file_write#4", native_file_write, NULL);
   fixscript_register_native_func(heap, "file_get_length#1", native_file_get_length, NULL);
   fixscript_register_native_func(heap, "file_get_position#1", native_file_get_position, NULL);
   fixscript_register_native_func(heap, "file_set_position#3", native_file_set_position, NULL);
   fixscript_register_native_func(heap, "file_exists#1", native_file_exists, NULL);

   fixscript_register_native_func(heap, "tcp_connection_open#2", native_tcp_connection_open, NULL);
   fixscript_register_native_func(heap, "tcp_connection_close#1", native_tcp_connection_close, NULL);
   fixscript_register_native_func(heap, "tcp_connection_read#5", native_tcp_connection_read, NULL);
   fixscript_register_native_func(heap, "tcp_connection_write#5", native_tcp_connection_write, NULL);

   fixscript_register_native_func(heap, "tcp_server_create#1", native_tcp_server_create, (void *)0);
   fixscript_register_native_func(heap, "tcp_server_create_local#1", native_tcp_server_create, (void *)1);
   fixscript_register_native_func(heap, "tcp_server_close#1", native_tcp_server_close, NULL);
   fixscript_register_native_func(heap, "tcp_server_accept#2", native_tcp_server_accept, NULL);

   fixscript_register_native_func(heap, "async_tcp_connection_open#4", native_async_tcp_connection_open, NULL);
   fixscript_register_native_func(heap, "async_tcp_connection_read#6", native_async_tcp_connection_read, NULL);
   fixscript_register_native_func(heap, "async_tcp_connection_write#6", native_async_tcp_connection_write, NULL);
   fixscript_register_native_func(heap, "async_tcp_connection_close#1", native_async_tcp_connection_close, NULL);
   fixscript_register_native_func(heap, "async_tcp_server_create#1", native_async_tcp_server_create, (void *)0);
   fixscript_register_native_func(heap, "async_tcp_server_create_local#1", native_async_tcp_server_create, (void *)1);
   fixscript_register_native_func(heap, "async_tcp_server_close#1", native_async_tcp_server_close, NULL);
   fixscript_register_native_func(heap, "async_tcp_server_accept#3", native_async_tcp_server_accept, NULL);
   fixscript_register_native_func(heap, "async_process#0", native_async_process, NULL);
   fixscript_register_native_func(heap, "async_process#1", native_async_process, NULL);
   fixscript_register_native_func(heap, "async_run_later#3", native_async_run_later, NULL);
   fixscript_register_native_func(heap, "async_quit#0", native_async_quit, NULL);
   fixscript_register_native_func(heap, "async_quit#1", native_async_quit, NULL);

   fixscript_register_native_func(heap, "process_create#4", native_process_create, NULL);
   fixscript_register_native_func(heap, "process_in_write#5", native_process_write, (void *)0);
   fixscript_register_native_func(heap, "process_out_read#5", native_process_read, (void *)1);
   fixscript_register_native_func(heap, "process_err_read#5", native_process_read, (void *)2);
   fixscript_register_native_func(heap, "process_in_read#4", native_process_read, (void *)0);
   fixscript_register_native_func(heap, "process_out_write#4", native_process_write, (void *)1);
   fixscript_register_native_func(heap, "process_err_write#4", native_process_write, (void *)2);
   fixscript_register_native_func(heap, "process_wait#1", native_process_wait, NULL);
   fixscript_register_native_func(heap, "process_kill#1", native_process_kill, NULL);
   fixscript_register_native_func(heap, "process_kill#2", native_process_kill, NULL);
   fixscript_register_native_func(heap, "process_is_running#1", native_process_is_running, NULL);
   fixscript_register_native_func(heap, "process_get_current_environment#0", native_process_get_current_environment, NULL);

   fixscript_register_native_func(heap, "clock_get_time#0", native_clock_get_time, (void *)0);
   fixscript_register_native_func(heap, "clock_get_micro_time#0", native_clock_get_time, (void *)1);
   fixscript_register_native_func(heap, "monotonic_get_time#0", native_monotonic_get_time, (void *)0);
   fixscript_register_native_func(heap, "monotonic_get_micro_time#0", native_monotonic_get_time, (void *)1);

   fixscript_register_native_func(heap, "array_create_view#3", native_array_create_view, NULL);
   fixscript_register_native_func(heap, "array_create_view#4", native_array_create_view, NULL);
}


static void event_thread(void *data)
{
   AsyncProcess *proc = data;

   for (;;) {
      wait_events(proc, -1);

      pthread_mutex_lock(&proc->foreign_mutex);
      proc->foreign_processed = 0;
      proc->foreign_notify_func(proc->foreign_notify_data);
      while (!proc->foreign_processed) {
         pthread_cond_wait(&proc->foreign_cond, &proc->foreign_mutex);
      }
      pthread_mutex_unlock(&proc->foreign_mutex);
   }
}


void fixio_integrate_event_loop(Heap *heap, IOEventNotifyFunc notify_func, void *notify_data)
{
   AsyncProcess *proc;
   Value error;

   proc = get_async_process(heap, &error);
   if (!proc || proc->foreign_notify_func) {
      fprintf(stderr, "foreign event loop already registered!\n");
      fflush(stderr);
      abort();
      return;
   }

   async_process_ref(proc);
   proc->foreign_notify_func = notify_func;
   proc->foreign_notify_data = notify_data;

   if (pthread_mutex_init(&proc->foreign_mutex, NULL) != 0) {
      fprintf(stderr, "can't initialize mutex for foreign event loop integration!\n");
      fflush(stderr);
      abort();
      return;
   }
   
   if (pthread_cond_init(&proc->foreign_cond, NULL) != 0) {
      fprintf(stderr, "can't initialize condition for foreign event loop integration!\n");
      fflush(stderr);
      abort();
      return;
   }

   if (!async_run_thread(event_thread, proc)) {
      fprintf(stderr, "can't create thread for foreign event loop integration!\n");
      fflush(stderr);
      abort();
      return;
   }
}


void fixio_process_events(Heap *heap)
{
   AsyncProcess *proc;
   Value error;

   proc = get_async_process(heap, &error);
   if (!proc || !proc->foreign_notify_func) {
      fprintf(stderr, "foreign event loop not registered!\n");
      fflush(stderr);
      abort();
      return;
   }
   
   if (!process_events(proc, heap, &error)) {
      fixscript_dump_value(heap, error, 1);
   }

   pthread_mutex_lock(&proc->foreign_mutex);
   proc->foreign_processed = 1;
   pthread_cond_signal(&proc->foreign_cond);
   pthread_mutex_unlock(&proc->foreign_mutex);
}
