#ifdef __VMS

#include "links.h"

#define __NEW_STARLET 1
#include <starlet.h>
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <descrip.h>
#include <dvidef.h>
#include <ttdef.h>
#include <tt2def.h>
#include <lib$routines.h>
#include <libdef.h>
#include <libclidef.h>

#if defined(HAVE_OPENSSL) && !defined(OPENSSL_NO_SHA) && !defined(OPENSSL_NO_SHA1)
#include <openssl/sha.h>
#define USE_SHA
#endif

#ifdef __VAX
struct _iosb {
	unsigned short iosb$w_status;
	unsigned short iosb$w_bcnt;
	unsigned iosb$l_dev_depend;
};
struct _generic_64 {
	unsigned gen64$l_longword[2];
};
struct _va_range {
	void *va_range$ps_start_va;
	void *va_range$ps_end_va;
};
#else
#include <iosbdef.h>
#include <gen64def.h>
#include <vadef.h>
#include <va_rangedef.h>
#endif

#include <pthread.h>
#include <tis.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

struct tt_mode {
	unsigned char class;
	unsigned char mode;
	unsigned short width;
	unsigned basic;
	unsigned extended;
};

#ifndef OPENVMS_64BIT

#define va_range _va_range
#define links_expreg	sys$expreg
#define links_cretva	sys$cretva
#define links_deltva	sys$deltva

#else

struct va_range {
	void *va_range$ps_start_va;
	void *va_range$ps_end_va;
};

static int links_expreg(size_t pagelets, struct va_range *retadr, unsigned ignore_mode, char ignore_reg)
{
	__int64 region_int = VA$C_P2;
	struct _generic_64 region;
	unsigned __int64 len;
	int vms_ret;

	memcpy(&region, &region_int, 8);

	vms_ret = sys$expreg_64(&region, pagelets << 9, 0, 0, &retadr->va_range$ps_start_va, &len);
	retadr->va_range$ps_end_va = (unsigned char *)retadr->va_range$ps_start_va + len - 1;

	return vms_ret;
}

static int links_cretva(struct va_range *creadr, struct va_range *retadr, unsigned int ignore_mode)
{
	__int64 region_int = VA$C_P2;
	struct _generic_64 region;
	unsigned __int64 len;
	int vms_ret;

	memcpy(&region, &region_int, 8);

	vms_ret = sys$cretva_64(&region, creadr->va_range$ps_start_va, (unsigned char *)creadr->va_range$ps_end_va - (unsigned char *)creadr->va_range$ps_start_va + 1, 0, 0, &retadr->va_range$ps_start_va, &len);
	retadr->va_range$ps_end_va = (unsigned char *)retadr->va_range$ps_start_va + len - 1;

	return vms_ret;
}

static int links_deltva(struct va_range *deladr, struct va_range *retadr, unsigned int ignore_mode)
{
	__int64 region_int = VA$C_P2;
	struct _generic_64 region;
	unsigned __int64 len;
	int vms_ret;

	memcpy(&region, &region_int, 8);

	vms_ret = sys$deltva_64(&region, deladr->va_range$ps_start_va, (unsigned char *)deladr->va_range$ps_end_va - (unsigned char *)deladr->va_range$ps_start_va + 1, 0, &retadr->va_range$ps_start_va, &len);
	retadr->va_range$ps_end_va = (unsigned char *)retadr->va_range$ps_start_va + len - 1;

	return vms_ret;
}

#endif

/* reimplement pipes because sockets + select have too big latency */
#define VMS_VIRTUAL_PIPE

/* use a special thread for select processing (if not defined, sockets
   are polled with a dynamic timer) */
#define VMS_SELECT_THREAD

#define VIRTUAL_PIPE_SIZE		512

#define GETTIMEOFDAY_POOL		256

/*#define TEST_WAKE_BUG*/
/*#define TRACE_PIPES*/

#undef read
#undef write
#undef pipe
#undef close
#undef select

#if !defined(VMS_VIRTUAL_PIPE) && defined(VMS_SELECT_THREAD)
#undef VMS_SELECT_THREAD
#endif

static void vms_fatal_exit(char *m, ...)
{
	va_list l;
	va_start(l, m);
	fprintf(stderr, "\n");
	vfprintf(stderr, cast_const_char m, l);
	fprintf(stderr, "%c\n", (unsigned char)7);
	fflush(stderr);
	va_end(l);
	exit(RET_FATAL);
}

void portable_sleep(unsigned msec)
{
	struct timespec tv;
	int rs;
	tv.tv_sec = msec / 1000;
	tv.tv_nsec = msec % 1000 * 1000000;
	if (!msec) tv.tv_nsec = 1;
	rs = pthread_delay_np(&tv);
	if (rs && rs != EINTR)
		vms_fatal_exit("pthread_delay_np failed: %d", rs);
}

static $DESCRIPTOR (output_channel_desc, "SYS$OUTPUT:");

void get_terminal_size(int *x, int *y)
{
	int ret;
	int x_code = DVI$_DEVBUFSIZ;
	int y_code = DVI$_TT_PAGE;
	unsigned long result;
	*x = 80;
	*y = 24;
	ret = lib$getdvi(&x_code, 0, &output_channel_desc, &result, 0, 0);
	if ($VMS_STATUS_SUCCESS(ret) && result) *x = result;
	ret = lib$getdvi(&y_code, 0, &output_channel_desc, &result, 0, 0);
	if ($VMS_STATUS_SUCCESS(ret) && result) *y = result;
}

static $DESCRIPTOR (display_desc, "DECW$DISPLAY");

int is_xterm(void)
{
	static int xt = -1;
	if (xt == -1) {
		xt = $VMS_STATUS_SUCCESS(lib$get_logical(&display_desc, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
	}
	return xt;
}

void get_path_to_exe(void)
{
	path_to_exe = cast_uchar "links";
}

static int io_raw = -1;
static int input_handle = -1;

static $DESCRIPTOR (input_channel_desc, "SYS$INPUT:");
static unsigned short input_channel;

static pthread_mutex_t io_mutex;
static pthread_mutex_t cancel_mutex;

static volatile struct timeval gettimeofday_pool[GETTIMEOFDAY_POOL];
static volatile unsigned gettimeofday_clock = 0;

#define INPUT_BUFFER_SIZE	16

static void vms_input_thread(void *n, int h)
{
	unsigned char buffer[INPUT_BUFFER_SIZE];
	int buffer_start = 0;
	int buffer_end = 0;
	unsigned ef;
	int vms_ret;
#ifndef VMS_VIRTUAL_PIPE
	/* VMS is buggy - if high priority thread blocks in write,
	   no low priority threads have a chance to run.
	   So, as a workaround, we set the socket nonblocking. */
	set_nonblock(h);
#endif
	vms_ret = lib$get_ef(&ef);
	if (!$VMS_STATUS_SUCCESS(vms_ret))
		vms_fatal_exit("lib$get_ef failed: %d", vms_ret);
	while (1) {
		int ret;
		int count;
		struct _iosb iosb;
		ret = pthread_mutex_lock(&cancel_mutex);
		if (ret)
			vms_fatal_exit("pthread_mutex_lock failed: %d", ret);
		ret = pthread_mutex_unlock(&cancel_mutex);
		if (ret)
			vms_fatal_exit("pthread_mutex_unlock failed: %d", ret);
		ret = pthread_mutex_lock(&io_mutex);
		if (ret)
			vms_fatal_exit("pthread_mutex_lock failed: %d", ret);
		vms_ret = sys$clref(ef);
		if (!$VMS_STATUS_SUCCESS(vms_ret))
			vms_fatal_exit("sys$clref failed: %d", vms_ret);
		vms_ret = sys$qio(ef, input_channel, IO$_READVBLK | IO$M_NOECHO | IO$M_NOFILTR, &iosb, tis_io_complete, 0, buffer + buffer_end, 1, 0, 0, 0, 0);
		if ($VMS_STATUS_SUCCESS(vms_ret)) {
			if (!iosb.iosb$w_status) {
				if (buffer_start != buffer_end) {
					hard_write(h, buffer + buffer_start, buffer_end - buffer_start);
					buffer_start = buffer_end;
				}
			}
			vms_ret = tis_synch(ef, &iosb);
		}
		ret = pthread_mutex_unlock(&io_mutex);
		if (ret)
			vms_fatal_exit("pthread_mutex_unlock failed: %d", ret);
		if (vms_ret == SS$_CANCEL ||
		    vms_ret == SS$_ABORT)
			continue;
		if (!$VMS_STATUS_SUCCESS(vms_ret))
			vms_fatal_exit("sys$qio failed: %d", vms_ret);
		if (!$VMS_STATUS_SUCCESS(iosb.iosb$w_status) &&
		    iosb.iosb$w_status != SS$_PARTESCAPE &&
		    iosb.iosb$w_status != SS$_DATAOVERUN &&
		    iosb.iosb$w_status != SS$_CANCEL &&
		    iosb.iosb$w_status != SS$_ABORT)
			vms_fatal_exit("IO$_READVBLK failed: %d, %d", (int)iosb.iosb$w_status, (int)iosb.iosb$w_bcnt);
		count = iosb.iosb$w_bcnt + (iosb.iosb$l_dev_depend >> 16);
		if (count == 1) {
			buffer_end++;
			if (buffer_start) {
				memmove(buffer, buffer + buffer_start, buffer_end - buffer_start);
				buffer_end -= buffer_start;
				buffer_start = 0;
			}
			if (buffer_end == INPUT_BUFFER_SIZE) {
				hard_write(h, buffer + buffer_start, buffer_end - buffer_start);
				buffer_start = buffer_end = 0;
			}
		}
	}
}

static void tty_passthru_mode(int enable)
{
	struct _iosb iosb;
	struct tt_mode mode;
	int ret;

	ret = sys$qiow(0, input_channel, IO$_SENSEMODE, &iosb, 0, 0, &mode, sizeof(mode), 0, 0, 0, 0);
	if (!$VMS_STATUS_SUCCESS(ret)) {
		error("IO$_SENSEMODE failed: %d", ret);
		return;
	}

	if (enable) mode.extended |= TT2$M_PASTHRU;
	else mode.extended &= ~TT2$M_PASTHRU;

	ret = sys$qiow(0, input_channel, IO$_SETMODE, &iosb, 0, 0, &mode, sizeof(mode), 0, 0, 0, 0);
	if (!$VMS_STATUS_SUCCESS(ret)) {
		error("IO$_SETMODE failed: %d", ret);
		return;
	}
}

int setraw(int ctl, int save)
{
	int ret;
	if (io_raw > 0)
		return 0;

	do_signal(SIGINT, SIG_IGN);

	ret = sys$assign(&input_channel_desc, &input_channel, 0, NULL);
	if (!$VMS_STATUS_SUCCESS(ret))
		vms_fatal_exit("sys$assign failed: %d", ret);
	io_raw = 1;

	tty_passthru_mode(1);

	ret = pthread_mutex_unlock(&io_mutex);
	if (ret)
		vms_fatal_exit("pthread_mutex_unlock failed: %d", ret);
	return 0;
}

void setcooked(int ctl)
{
	int ret;
	if (io_raw <= 0)
		return;
	ret = pthread_mutex_lock(&cancel_mutex);
	if (ret)
		vms_fatal_exit("pthread_mutex_lock failed: %d", ret);
	clear_events(input_handle, 1);
	while ((ret = pthread_mutex_trylock(&io_mutex))) {
		if (ret != EBUSY)
			vms_fatal_exit("pthread_mutex_trylock failed: %d", ret);
		ret = sys$cancel(input_channel);
		if (!$VMS_STATUS_SUCCESS(ret))
			vms_fatal_exit("sys$cancel failed: %d", ret);
		clear_events(input_handle, 1);
		portable_sleep(0);
	}
	ret = pthread_mutex_unlock(&cancel_mutex);
	if (ret)
		vms_fatal_exit("pthread_mutex_unlock failed: %d", ret);

	tty_passthru_mode(0);

	io_raw = 0;
	ret = sys$dassgn(input_channel);
	if (!$VMS_STATUS_SUCCESS(ret))
		vms_fatal_exit("sys$dassgn failed: %d", ret);
}

int get_input_handle(void)
{
	int ret;
	if (input_handle >= 0)
		return input_handle;
	ret = pthread_mutex_init(&io_mutex, NULL);
	if (ret)
		vms_fatal_exit("pthread_mutex_init failed: %d", ret);
	ret = pthread_mutex_init(&cancel_mutex, NULL);
	if (ret)
		vms_fatal_exit("pthread_mutex_init failed: %d", ret);
	ret = pthread_mutex_lock(&io_mutex);
	if (ret)
		vms_fatal_exit("pthread_mutex_lock failed: %d", ret);
	vms_thread_high_priority = 1;
	input_handle = start_thread(vms_input_thread, NULL, 0, 0);
	if (input_handle == -1)
		vms_fatal_exit("unable to start keyboard thread");
	vms_thread_high_priority = 0;
	return input_handle;
}

#if defined(GRDRV_X)

static int x11_ef = -1;
static int x11_pipe = -1;

static void vms_x11_thread(void *n, int h)
{
	int ret;
	set_nonblock(h);
	while (1) {
		ret = sys$clref(x11_ef);
		if (!$VMS_STATUS_SUCCESS(ret))
			vms_fatal_exit("sys$clref failed: %d", ret);
		EINTRLOOP(ret, (int)vms_write(h, "", 1));
		ret = sys$waitfr(x11_ef);
		if (!$VMS_STATUS_SUCCESS(ret))
			vms_fatal_exit("sys$waitfr failed: %d", ret);
	}
}

int vms_x11_fd(int ef)
{
	if (x11_pipe >= 0) {
		if (ef != x11_ef) vms_fatal_exit("requesting multiple event flags: %d, %d", x11_ef, ef);
		return x11_pipe;
	}
	x11_ef = ef;
	x11_pipe = start_thread(vms_x11_thread, NULL, 0, 0);
	if (x11_pipe == -1)
		vms_fatal_exit("unable to start Xwindow event thread");
	set_nonblock(x11_pipe);
	return x11_pipe;
}

#endif

#if !defined(VMS_VIRTUAL_PIPE) || defined(VMS_SELECT_THREAD)

static int vms_socketpair(int *fd)
{
#if defined(HAVE_SOCKETPAIR)
	int r;
	EINTRLOOP(r, socketpair(AF_INET, SOCK_STREAM, 0, fd));
	return r;
#else
#define PIPE_RETRIES	10
	int rs;
	int s1, s2, s3;
	socklen_t l;
	struct sockaddr_in sa1, sa2;
	int retry_count = 0;
	again:
	s1 = c_socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s1 < 0)
		goto err0;
	s2 = c_socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s2 < 0)
		goto err1;
	memset(&sa1, 0, sizeof(sa1));
	sa1.sin_family = AF_INET;
	sa1.sin_port = htons(0);
	sa1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	EINTRLOOP(rs, bind(s1, (struct sockaddr *)&sa1, sizeof(sa1)));
	if (rs)
		goto err2;
	l = sizeof(sa2);
	EINTRLOOP(rs, getsockname(s1, (struct sockaddr *)&sa1, &l));
	if (rs)
		goto err2;
	EINTRLOOP(rs, listen(s1, 1));
	if (rs)
		goto err2;
	EINTRLOOP(rs, connect(s2, (struct sockaddr *)&sa1, sizeof(sa1)));
	if (rs)
		goto err2;
	l = sizeof(sa1);
	EINTRLOOP(rs, getsockname(s2, (struct sockaddr *)&sa1, &l));
	if (rs)
		goto err2;
	l = sizeof(sa2);
	s3 = c_accept(s1, (struct sockaddr *)&sa2, &l);
	if (s3 < 0)
		goto err2;
	if (sa1.sin_addr.s_addr != sa2.sin_addr.s_addr ||
	    sa1.sin_port != sa2.sin_port) {
		errno = EINVAL;
		goto err2;
	}
	EINTRLOOP(rs, close(s1));
	fd[0] = s2;
	fd[1] = s3;
	return 0;
	err2:
	EINTRLOOP(rs, close(s2));
	err1:
	EINTRLOOP(rs, close(s1));
	err0:
	if (++retry_count > PIPE_RETRIES) return -1;
	portable_sleep(100);
	goto again;
#endif
}

#endif

#ifdef VMS_VIRTUAL_PIPE

static pthread_mutex_t pipe_mutex;
static pthread_cond_t pipe_cond;

static void pipe_lock(void)
{
	int ret;
	ret = pthread_mutex_lock(&pipe_mutex);
	if (ret)
		vms_fatal_exit("pthread_mutex_lock failed: %d", ret);
}

static void pipe_unlock(void)
{
	int ret;
	ret = pthread_mutex_unlock(&pipe_mutex);
	if (ret)
		vms_fatal_exit("pthread_unmutex_lock failed: %d", ret);
}

static void pipe_unlock_wait_cond(pthread_cond_t *cond)
{
	int ret;
	ret = pthread_cond_wait(cond, &pipe_mutex);
	if (ret && ret != EINTR)
		vms_fatal_exit("pthread_cond_wait failed: %d", ret);
}

static void pipe_unlock_wait(void)
{
	pipe_unlock_wait_cond(&pipe_cond);
}

static int pipe_unlock_wait_time(struct timespec *ts)
{
	int ret;
	ret = pthread_cond_timedwait(&pipe_cond, &pipe_mutex, ts);
	if (ret && ret != EINTR && ret != ETIMEDOUT)
		vms_fatal_exit("pthread_cond_timedwait failed: %d", ret);
	return ret == ETIMEDOUT;
}

static void pipe_wake_cond(pthread_cond_t *cond)
{
	int ret;
	ret = pthread_cond_broadcast(cond);
	if (ret)
		vms_fatal_exit("pthread_cond_broadcast failed: %d", ret);
}

static void pipe_wake(void)
{
	pipe_wake_cond(&pipe_cond);
}

#include "vpipe.inc"

static void get_abstime(time_t sec, unsigned usec, struct timespec *ts)
{
#if 0
	int ret;
	struct timespec exp;
	exp.tv_sec = sec;
	exp.tv_nsec = usec * 1000;
	ret = pthread_get_expiration_np(&exp, ts);
	if (ret)
		vms_fatal_exit("pthread_get_expiration_np failed: %d", ret);
#else
	int ret;
	struct timeval tv;
	ret = gettimeofday(&tv, NULL);
	if (ret)
		vms_fatal_exit("gettimeofday failed: %d", errno);
	tv.tv_sec += sec;
	tv.tv_usec += usec;
	if (tv.tv_usec >= 1000000) {
		tv.tv_usec -= 1000000;
		tv.tv_sec++;
	}
	{
		unsigned c = gettimeofday_clock;
		if (c >= GETTIMEOFDAY_POOL) c = 0;
		gettimeofday_clock = c;
		gettimeofday_pool[c].tv_sec = (unsigned long)gettimeofday_pool[c].tv_sec * 11 + (unsigned long)tv.tv_sec;
		gettimeofday_pool[c].tv_usec = (unsigned long)gettimeofday_pool[c].tv_usec * 11 + (unsigned long)tv.tv_usec;
	}
	ts->tv_sec = tv.tv_sec;
	ts->tv_nsec = tv.tv_usec * 1000;
#endif
}

static int check_fd(int fd, int what)
{
	fd_set fds;
	struct timeval tv = {0, 0};
	int rs;
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
	EINTRLOOP(rs, select(fd + 1,
		what == 0 ? &fds : NULL,
		what == 1 ? &fds : NULL,
		what == 2 ? &fds : NULL,
		&tv));
	if (rs < 0) vms_fatal_exit("select for %d/%d write failed: %d", fd, what, errno);
	return rs;
}

#ifdef VMS_SELECT_THREAD

static pthread_cond_t select_cond;

static int select_thread_signal[2];

static int select_set_max;

static fd_set select_set_read;
static fd_set select_set_write;
static fd_set select_set_exception;

static fd_set add_set_read;
static fd_set add_set_write;
static fd_set add_set_exception;

static fd_set remove_set;

static fd_set rs, ws, es;

static void add_to_thread_set(int fd, fd_set *s)
{
	FD_SET(fd, s);
	if (fd >= select_set_max)
		select_set_max = fd + 1;
}

static void vms_select_thread(void *p, int n)
{
	int should_wake = 0;
	pipe_lock();
	FD_ZERO(&rs);
	FD_ZERO(&ws);
	FD_ZERO(&es);
	while (1) {
		int i;
		int r;
		int need_select = 0;
		int new_max = 0;
		for (i = 0; i < select_set_max; i++) {
			int set;
			if (FD_ISSET(i, &add_set_read)) {
				FD_CLR(i, &add_set_read);
				FD_SET(i, &select_set_read);
			}
			if (FD_ISSET(i, &add_set_write)) {
				FD_CLR(i, &add_set_write);
				FD_SET(i, &select_set_write);
			}
			if (FD_ISSET(i, &add_set_exception)) {
				FD_CLR(i, &add_set_exception);
				FD_SET(i, &select_set_exception);
			}
			if (FD_ISSET(i, &remove_set)) {
				FD_CLR(i, &remove_set);
#ifndef TEST_WAKE_BUG
				FD_CLR(i, &select_set_read);
				FD_CLR(i, &select_set_write);
				FD_CLR(i, &select_set_exception);
#endif
				should_wake = 1;
			}
			set = 0;
			if (FD_ISSET(i, &select_set_read)) {
				set = 1;
				FD_SET(i, &rs);
			}
			if (FD_ISSET(i, &select_set_write)) {
				set = 1;
				FD_SET(i, &ws);
			}
			if (FD_ISSET(i, &select_set_exception)) {
				set = 1;
				FD_SET(i, &es);
			}
			if (set && i >= new_max)
				new_max = i + 1;
			if (set && i != select_thread_signal[0])
				need_select = 1;
		}
		select_set_max = new_max;
		if (!need_select) {
			if (should_wake) {
				pipe_wake();
				should_wake = 0;
			}
			pipe_unlock_wait_cond(&select_cond);
			clear_events(select_thread_signal[0], 0);
		} else {
			pipe_unlock();
			if (should_wake) {
#ifdef TEST_WAKE_BUG
				fprintf(stderr, "wake 1. ");
#endif
				pipe_wake();
#ifdef TEST_WAKE_BUG
				fprintf(stderr, "wake 2. ");
#endif
				should_wake = 0;
			}
			/* It is needed to sleep here with pthread_yield_np() or
			   portable_sleep(0), otherwise some bug in decthreads
			   is triggered and the main thread isn't woken up.
			   tis_io_complete apparently seems to fix the bug too,
			   so we do a dummy syscall that calls tis_io_complete
			   as an AST */
			{
				struct _generic_64 priv;
				memset(&priv, 0, sizeof priv);
				sys$check_privilegew(0, &priv, NULL, 0, NULL, NULL, tis_io_complete, 0);
			}
			/*tis_io_complete();*/
			/*fprintf(stderr, "time1 %d.   ", get_time());
			pthread_yield_np();
			portable_sleep(0);
			fprintf(stderr, "time2 %d.   ", get_time());*/
#ifdef TEST_WAKE_BUG
			fprintf(stderr, "wake 3. ");
#endif
			EINTRLOOP(r, select(select_set_max, &rs, &ws, &es, NULL));
#ifdef TEST_WAKE_BUG
			fprintf(stderr, "wake 4. ");
#endif
			if (r < 0)
				vms_fatal_exit("select in thread failed: %d", errno);
			pipe_lock();
			for (i = 0; i < select_set_max; i++) {
				if (i == select_thread_signal[0]) {
					if (FD_ISSET(i, &rs))
						clear_events(i, 0);
					continue;
				}
				if (FD_ISSET(i, &rs)) {
					FD_CLR(i, &rs);
					FD_CLR(i, &select_set_read);
					should_wake = 1;
				}
				if (FD_ISSET(i, &ws)) {
					FD_CLR(i, &ws);
					FD_CLR(i, &select_set_write);
					should_wake = 1;
				}
				if (FD_ISSET(i, &es)) {
					FD_CLR(i, &es);
					FD_CLR(i, &select_set_exception);
					should_wake = 1;
				}
			}
		}
	}
}

static void select_thread_wake(void)
{
	int r;
	EINTRLOOP(r, (int)write(select_thread_signal[1], "", 1));
	pipe_wake_cond(&select_cond);
}

#endif

#ifndef VMS_SELECT_THREAD

#ifdef __VAX
#define MIN_WAIT_TIME	10000
#else
#define MIN_WAIT_TIME	900
#endif
#define MAX_WAIT_TIME	999999

static unsigned wait_time = MIN_WAIT_TIME;

static void increase_dynamic_time(void)
{
	wait_time += (wait_time / 2);
	if (wait_time > MAX_WAIT_TIME) wait_time = MAX_WAIT_TIME;
}

static void decrease_dynamic_time(void)
{
	wait_time /= 2;
	if (wait_time > MIN_WAIT_TIME) wait_time = MIN_WAIT_TIME;
}

#endif

int vms_select(int n, fd_set *rs, fd_set *ws, fd_set *es, struct timeval *t)
{
	struct timespec ts;
	int i;
	int last_pass = 0;
	int ret_cnt = 0;
	int dynamic_wait;
#ifndef VMS_SELECT_THREAD
	int dynamically_waited = 0;
#endif
	/*if (!rs && !ws && !es)
		return select(0, NULL, NULL, NULL, t);*/
	if (t) {
		if (!t->tv_sec && !t->tv_usec) {
			last_pass = 1;
		} else {
			get_abstime(t->tv_sec, t->tv_usec, &ts);
		}
	}
	pipe_lock();
	test_again:
	dynamic_wait = 0;
	for (i = 0; i < n; i++) {
		int ts = 0;
		if (rs && FD_ISSET(i, rs)) {
			int signaled = 0;
			if (pipe_desc[i]) {
				signaled |= vpipe_may_read(i);
			} else {
#ifdef VMS_SELECT_THREAD
				if (FD_ISSET(i, &select_set_read)) dynamic_wait |= 1;
				else
#endif
				if (check_fd(i, 0)) signaled = 1;
				else {
#ifdef VMS_SELECT_THREAD
					add_to_thread_set(i, &add_set_read);
#endif
					dynamic_wait |= 3;
				}
			}
			if (!last_pass) {
				if (signaled) {
					clear_inactive(rs, i);
					clear_inactive(ws, i);
					clear_inactive(es, i);
					last_pass = 1;
				}
			} else {
				if (!signaled) FD_CLR(i, rs);
			}
			ts |= signaled;
		}
		if (ws && FD_ISSET(i, ws)) {
			int signaled = 0;
			if (pipe_desc[i]) {
				signaled |= vpipe_may_write(i);
			} else {
#ifdef VMS_SELECT_THREAD
				if (FD_ISSET(i, &select_set_write)) dynamic_wait |= 1;
				else
#endif
				if (check_fd(i, 1)) signaled = 1;
				else {
#ifdef VMS_SELECT_THREAD
					add_to_thread_set(i, &add_set_write);
#endif
					dynamic_wait |= 3;
				}
			}
			if (!last_pass) {
				if (signaled) {
					clear_inactive(rs, i + 1);
					clear_inactive(ws, i);
					clear_inactive(es, i);
					last_pass = 1;
				}
			} else {
				if (!signaled) FD_CLR(i, ws);
			}
			ts |= signaled;
		}
		if (es && FD_ISSET(i, es)) {
			int signaled = 0;
			if (pipe_desc[i]) {
			} else {
#ifdef VMS_SELECT_THREAD
				if (FD_ISSET(i, &select_set_exception)) dynamic_wait |= 1;
				else
#endif
				if (check_fd(i, 2)) signaled = 1;
				else {
#ifdef VMS_SELECT_THREAD
					add_to_thread_set(i, &add_set_exception);
					dynamic_wait |= 3;
#endif
				}
			}
			if (!last_pass) {
				if (signaled) {
					clear_inactive(rs, i + 1);
					clear_inactive(ws, i + 1);
					clear_inactive(es, i);
					last_pass = 1;
				}
			} else {
				if (!signaled) FD_CLR(i, es);
			}
			ts |= signaled;
		}
		if (last_pass) ret_cnt += ts;
	}
	if (!last_pass) {
#ifndef VMS_SELECT_THREAD
		if (dynamic_wait) {
			struct timespec ts2;
			if (dynamically_waited)
				increase_dynamic_time();
			/*fprintf(stderr, "wait time: %d\n", wait_time);*/
			get_abstime(0, wait_time, &ts2);
			if (t &&
			    (ts2.tv_sec > ts.tv_sec ||
			    (ts2.tv_sec == ts.tv_sec && ts2.tv_nsec > ts.tv_nsec)))
				goto full_wait;
			dynamically_waited = pipe_unlock_wait_time(&ts2);
			goto test_again;
		}
#else
		if (dynamic_wait & 2)
			select_thread_wake();
#endif
#ifndef VMS_SELECT_THREAD
		dynamically_waited = 0;
#endif
		if (!t) {
			pipe_unlock_wait();
			goto test_again;
		} else {
full_wait:
			if (pipe_unlock_wait_time(&ts))
				last_pass = 1;
			goto test_again;
		}
	}
#ifndef VMS_SELECT_THREAD
	if (dynamically_waited)
		decrease_dynamic_time();
#endif
	pipe_unlock();
	return ret_cnt;
}

#else

int vms_select(int n, fd_set *rs, fd_set *ws, fd_set *es, struct timeval *t)
{
	return select(n, rs, ws, es, t);
}

#endif

int vms_close(int fd)
{
#ifdef VMS_VIRTUAL_PIPE
	int r = vpipe_close(fd);
	if (r != -2) return r;
#ifdef VMS_SELECT_THREAD
	pipe_lock();
	while (1) {
		FD_CLR(fd, &add_set_read);
		FD_CLR(fd, &add_set_write);
		FD_CLR(fd, &add_set_exception);
		if (!(FD_ISSET(fd, &select_set_read) ||
		      FD_ISSET(fd, &select_set_write) ||
		      FD_ISSET(fd, &select_set_exception)))
			break;
		add_to_thread_set(fd, &remove_set);
#ifdef TEST_WAKE_BUG
		fprintf(stderr, "close 1. ");
#endif
		select_thread_wake();
#ifdef TEST_WAKE_BUG
		fprintf(stderr, "close 2. ");
#endif
		pipe_unlock_wait();
#ifdef TEST_WAKE_BUG
		fprintf(stderr, "close 3. ");
#endif
	}
	pipe_unlock();
#endif
#endif
	return close(fd);
}

int vms_pipe(int fd[2])
{
#ifdef VMS_VIRTUAL_PIPE
	return vpipe_create(fd);
#else
	return vms_socketpair(fd);
#endif
}

void set_nonblock(int fd)
{
#ifdef VMS_VIRTUAL_PIPE
	if (get_virtual_pipe(fd)) {
		pipe_flags[fd] |= VIRTUAL_PIPE_FLAG_NONBLOCK;
		pipe_unlock();
		pipe_wake();
	} else
#endif
	{
		int rs;
		int on = 1;
		EINTRLOOP(rs, ioctl(fd, FIONBIO, &on));
	}
}

#define BOUNCE_BUFFER_SIZE	64

int vms_read(int fd, void *buf, size_t size)
{
#ifdef VMS_VIRTUAL_PIPE
	int r = vpipe_read(fd, buf, size);
	if (r != -2) return r;
#endif
#ifdef OPENVMS_64BIT
	if ((my_uintptr_t)buf + size >= 0x80000000U) {
		int r;
		unsigned char static_buffer[BOUNCE_BUFFER_SIZE];
		unsigned char *bounce_buffer = NULL;
		if (size > BOUNCE_BUFFER_SIZE)
			bounce_buffer = _malloc32(size);
		if (!bounce_buffer) {
			bounce_buffer = static_buffer;
			if (size > BOUNCE_BUFFER_SIZE) size = BOUNCE_BUFFER_SIZE;
		}
		r = read(fd, bounce_buffer, size);
		if (r > 0) memcpy(buf, bounce_buffer, r);
		if (bounce_buffer != static_buffer)
			free(bounce_buffer);
		return r;
	}
#endif
	return read(fd, buf, size);
}

int vms_write(int fd, const void *buf, size_t size)
{
#ifdef VMS_VIRTUAL_PIPE
	int r = vpipe_write(fd, buf, size);
	if (r != -2) return r;
#endif
#ifdef OPENVMS_64BIT
	if ((my_uintptr_t)buf + size >= 0x80000000U) {
		int r;
		unsigned char static_buffer[BOUNCE_BUFFER_SIZE];
		unsigned char *bounce_buffer = NULL;
		if (size > BOUNCE_BUFFER_SIZE)
			bounce_buffer = _malloc32(size);
		if (!bounce_buffer) {
			bounce_buffer = static_buffer;
			if (size > BOUNCE_BUFFER_SIZE) size = BOUNCE_BUFFER_SIZE;
		}
		memcpy(bounce_buffer, buf, size);
		r = write(fd, bounce_buffer, size);
		if (bounce_buffer != static_buffer)
			free(bounce_buffer);
		return r;
	}
#endif
	return write(fd, buf, size);
}

#ifdef VMS_ADVANCED_HEAP

struct region {
	unsigned char *start;
	size_t len;
	int allocated;
};

static struct region *regions = NULL;
static int n_used_regions = 0;
static int n_allocated_regions = 0;
static unsigned char *max_expreg = NULL;

static int expand_regions(void)
{
	struct region *regs;
	if (n_used_regions < n_allocated_regions)
		return 0;
	if (n_allocated_regions >= MAXINT / 2 / sizeof(struct region))
		return -1;
	regs = realloc(regions, (n_allocated_regions + 1) * sizeof(struct region));
	if (!regs)
		return -1;
	regions = regs;
	n_allocated_regions++;
	return 0;
}

void *virtual_alloc(size_t len)
{
	int i;
	struct va_range expr, cre_range;
	int vms_ret;

search_again:
	for (i = 0; i < n_used_regions; i++) {
		if (regions[i].allocated)
			continue;
		if (regions[i].len < len)
			continue;
		if (regions[i].len == len) {
			regions[i].allocated = 1;
			goto ret_st;
		}
		if (expand_regions())
			return NULL;
		memmove(&regions[i + 1], &regions[i], (n_used_regions - i) * sizeof(struct region));
		n_used_regions++;
		regions[i].len = len;
		regions[i].allocated = 1;
		regions[i + 1].start = regions[i].start + len;
		regions[i + 1].len -= len;

ret_st:
		cre_range.va_range$ps_start_va = regions[i].start;
		cre_range.va_range$ps_end_va = regions[i].start + len - 1;
		vms_ret = links_cretva(&cre_range, &expr, 0);
		if (vms_ret != SS$_NORMAL) {
			/*fprintf(stderr, "cretva failed\n");*/
			regions[i].allocated = 0;
			return NULL;
		}

		/*fprintf(stderr, "allocated: %p,%p ; %p,%p\n", cre_range.va_range$ps_start_va, cre_range.va_range$ps_end_va, expr.va_range$ps_start_va, expr.va_range$ps_end_va);*/

		/*fprintf(stderr, "found existing (%d)\n", n_used_regions);*/
		/*memset(regions[i].start, 0, regions[i].len);*/

		return regions[i].start;
	}
	if (expand_regions())
		return NULL;
	vms_ret = links_expreg(len >> 9, &expr, 0, 0);
	if (vms_ret != SS$_NORMAL) {
		return NULL;
	}
	/*fprintf(stderr, "expreg (%p,%p %p) (%d)\n", expr.va_range$ps_start_va, expr.va_range$ps_end_va, (void *)len, n_used_regions);*/
	max_expreg = expr.va_range$ps_end_va;
	regions[n_used_regions].start = expr.va_range$ps_start_va;
	regions[n_used_regions].len = (unsigned char *)expr.va_range$ps_end_va - (unsigned char *)expr.va_range$ps_start_va + 1;
	regions[n_used_regions].allocated = 1;
	n_used_regions++;
	if (len != regions[n_used_regions - 1].len) {
		/*fprintf(stderr, "size mismatch\n");*/
		regions[n_used_regions - 1].allocated = 0;
		return NULL;
	}
	return expr.va_range$ps_start_va;
}

void virtual_free(void *ptr, size_t len)
{
	int i;
	struct va_range del_range, del_ret;
	int vms_ret;

	for (i = 0; i < n_used_regions; i++) {
		if (!regions[i].allocated)
			continue;
		if (regions[i].start == ptr && regions[i].len == len)
			goto found;
	}
	vms_fatal_exit("alloaction %p, %p not found", ptr, (void *)len);
found:
	/*fprintf(stderr, "dealloc (%d - %p,%p) (%d)\n", i, ptr, (void *)len, n_used_regions);*/
	regions[i].allocated = 0;
	if (i < n_used_regions - 1 && !regions[i + 1].allocated && regions[i].start + regions[i].len == regions[i + 1].start) {
		regions[i].len += regions[i + 1].len;
		memmove(&regions[i + 1], &regions[i + 2], (n_used_regions - i - 2) * sizeof(struct region));
		n_used_regions--;
		/*fprintf(stderr, "front merge\n");*/
	}
	if (i && !regions[i - 1].allocated && regions[i - 1].start + regions[i - 1].len == regions[i].start) {
		regions[i - 1].len += regions[i].len;
		memmove(&regions[i], &regions[i + 1], (n_used_regions - 1 - i) * sizeof(struct region));
		n_used_regions--;
		/*fprintf(stderr, "back merge\n");*/
	}

	del_range.va_range$ps_start_va = ptr;
	del_range.va_range$ps_end_va = (unsigned char *)ptr + (len - 1);
	if (del_range.va_range$ps_end_va == max_expreg) {
		del_range.va_range$ps_end_va = (unsigned char *)del_range.va_range$ps_end_va - page_size;
		memset((unsigned char *)del_range.va_range$ps_end_va + 1, 0, page_size);
	}
	if (del_range.va_range$ps_start_va <= del_range.va_range$ps_end_va) {
		vms_ret = links_deltva(&del_range, &del_ret, 0);
		if (vms_ret != SS$_NORMAL)
			vms_fatal_exit("deltva failed: %d", vms_ret);
	}
}

#endif

void init_os(void)
{
#if !defined(__VAX) && defined(__FEATURE_MODE_INIT_STATE) && defined(__FEATURE_MODE_CURVAL)
	if (decc$feature_get("DECC$EFS_CHARSET", __FEATURE_MODE_INIT_STATE) < 1)
		decc$feature_set("DECC$EFS_CHARSET", __FEATURE_MODE_CURVAL, 1);
#endif
#if defined(OPENVMS_64BIT)
	{
		int i;
		char **new_argv = malloc((g_argc + 1) * sizeof(char *));
		if (!new_argv) vms_fatal_exit("can't allocate argv");
		for (i = 0; i <= g_argc; i++)
			new_argv[i] = (char *)(my_intptr_t)(((unsigned *)g_argv)[i]);
		g_argv = new_argv;
	}
#endif
#ifdef VMS_VIRTUAL_PIPE
	{
		int ret;
		ret = pthread_mutex_init(&pipe_mutex, NULL);
		if (ret)
			vms_fatal_exit("pthread_mutex_init failed: %d", ret);
		ret = pthread_cond_init(&pipe_cond, NULL);
		if (ret)
			vms_fatal_exit("pthread_cond_init failed: %d", ret);
#ifdef VMS_SELECT_THREAD
		ret = pthread_cond_init(&select_cond, NULL);
		if (ret)
			vms_fatal_exit("pthread_cond_init failed: %d", ret);
		ret = vms_socketpair(select_thread_signal);
		if (ret)
			vms_fatal_exit("can't create select thread socket");
		set_nonblock(select_thread_signal[0]);
		set_nonblock(select_thread_signal[1]);
		select_set_max = 0;
		FD_ZERO(&select_set_read);
		FD_ZERO(&select_set_write);
		FD_ZERO(&select_set_exception);
		FD_ZERO(&add_set_read);
		FD_ZERO(&add_set_write);
		FD_ZERO(&add_set_exception);
		FD_ZERO(&remove_set);
		add_to_thread_set(select_thread_signal[0], &select_set_read);
		select_set_max = select_thread_signal[0] + 1;
		vms_thread_high_priority = -1;
		ret = start_thread(vms_select_thread, NULL, 0, 0);
		if (ret == -1)
			vms_fatal_exit("unable to start select thread");
		vms_thread_high_priority = 0;
#endif
	}
#endif
}

void os_seed_random(unsigned char **pool, int *pool_size)
{
	struct history_item *hi;
	struct list_head *lhi;
	DIR *dir;
	int n, h;

	*pool = init_str();
	*pool_size = 0;

	/*
	 * This is not very secure, but I don't know a better way.
	 */
	add_bytes_to_str(pool, pool_size, (unsigned char *)(void *)&gettimeofday_pool, sizeof gettimeofday_pool);

#ifdef USE_SHA
	/*
	 * Make sure that even if the transformation is reversible (due to
	 * poor randomness on OpenVMS), the adversary won't be able to find
	 * any URLs in the history.
	 */
	n = 0;
	foreach(struct history_item, hi, lhi, goto_url_history.items) {
		SHA_CTX ctx;
		unsigned char result[SHA_DIGEST_LENGTH];
		unsigned char sum;
		int i;

		SHA1_Init(&ctx);
		SHA1_Update(&ctx, hi->str, strlen(cast_const_char hi->str));
		SHA1_Final(result, &ctx);

		sum = 0;
		for (i = 0; i < SHA_DIGEST_LENGTH; i++) {
			sum += result[i] + (result[i] >> 4);
		}
		add_chr_to_str(pool, pool_size, sum & 0xf);
		if (++n >= 64)
			break;
	}
#endif

	dir = c_opendir(cast_uchar "/SYS$LOGIN");
	if (dir) {
		for (n = 0; n < 256; n++) {
			struct dirent *de;
			unsigned char *path;
			struct stat st;
			ENULLLOOP(de, (void *)readdir(dir));
			if (!de)
				break;
			path = stracpy(cast_uchar "/SYS$LOGIN/");
			add_to_strn(&path, cast_uchar de->d_name);
			if (!stat(cast_const_char path, &st)) {
				add_bytes_to_str(pool, pool_size, (unsigned char *)&st.st_ctime, (int)sizeof st.st_ctime);
			}
			mem_free(path);
		}
		closedir(dir);
	}

	h = c_open(cast_uchar "/SYS$LOGIN/SSH2/RANDOM_SEED", O_RDONLY);
	if (h == -1)
		h = c_open(cast_uchar "/SYS$LOGIN/SSH/RANDOM_SEED", O_RDONLY);
	if (h != -1) {
		unsigned char buffer[512];
		int r;
		r = hard_read(h, buffer, (int)sizeof buffer);
		if (r >= 0)
			add_bytes_to_str(pool, pool_size, buffer, r);
		EINTRLOOP(r, close(h));
	}
}

void terminate_osdep(void)
{
}

#else

typedef int vms_c_no_empty_unit;

#endif
