view upreckon/_unixmodule.cpp @ 193:a76cdc26ba9d

Added conf. var. match and match='regexp' for non-archives Specify match='regexp', and your tests and dummies will be treated as regular expressions describing test case identifiers. Every file that is in a suitable location and whose name matches {testcase,dummy}inname and the given regexp will be treated as a file with test case input data. You are free to use backreferences in the regexps, but group numbering starts at two rather than one. If you want test groups, you can get them magically created for you by putting a part of the test ID in a group in the regexp sense and specifying the tests variable as a pair consisting of the regexp itself and the number of this regexp group (remember group numbers start at two).
author Oleg Oshmyan <chortos@inbox.lv>
date Thu, 11 Aug 2011 23:20:52 +0300
parents 4f69e30abbd5
children 8c4e92fb32d8
line wrap: on
line source

// Copyright (c) 2011 Chortos-2 <chortos@inbox.lv>

#include <Python.h>
#include <structmember.h>
#include <stdio.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include <limits.h>

#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#ifdef HAVE_SPAWN_H
#include <spawn.h>
#ifdef __APPLE__
#pragma weak_import posix_spawnp
#endif
#endif

#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif

#if !(defined __cplusplus) && !(defined bool)
#ifdef HAVE_C99_BOOL
#define bool _Bool
#else
#define bool char
#endif
#undef true
#define true 1
#undef false
#define false 0
#endif

// On Python 2.5, SIGINT handling may get delayed until we return to Python
#if PY_MAJOR_VERSION > 2 || PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6
#define USE_WAKEUP_FD
#endif

#if !(defined RLIMIT_AS) && defined RLIMIT_VMEM
#define RLIMIT_AS RLIMIT_VMEM
#endif

// Condition stolen from posixmodule.c of Python 2.7.1
#if defined __USLC__ && defined __SCO_VERSION__  // SCO UDK Compiler
//#ifdef HAVE_FORK1
#define fork fork1
#endif

// Stolen from posixmodule.c of Python 2.7.1
#ifdef WITH_NEXT_FRAMEWORK
#include <crt_externs.h>
static char **environ = NULL;
#elif !(defined _MSC_VER) && (!(defined __WATCOMC__) || defined __QNX__)
extern char **environ;
#endif

#ifndef Py_PYTIME_H
typedef struct timeval _PyTime_timeval;
#ifndef GETTIMEOFDAY_NO_TZ
#define _PyTime_gettimeofday(tvp) gettimeofday((tvp), NULL)
#else
#define _PyTime_gettimeofday(tvp) gettimeofday((tvp))
#endif
#endif

#if PY_MAJOR_VERSION >= 3
#define PyInt_AsLong PyLong_AsLong
#define PyInt_FromLong PyLong_FromLong
#define PyNumber_Int PyNumber_Long
#endif

#define TESTEE_SPAWNED 0
#define TESTEE_SPAWN_FAILED 1
#define TESTEE_REPORT_STATUS(status) \
	do \
	{ \
		const char c = (status); \
		write(c2ppipe[1], &c, 1); \
	} \
	while (0)

#if !(defined SIGKILL) && defined SIGTERM
#define SIGKILL SIGTERM
#endif

#if defined HAVE_KILL && defined SIGKILL
#ifdef HAVE_WAITPID
#define TERM_TESTEE \
	do \
	{ \
		kill(-curpid, SIGKILL); \
		kill(-curpid, SIGCONT); \
		while (waitpid(curpid, &retstat, 0) != curpid); \
	} \
	while (0)
#else
#define TERM_TESTEE \
	do \
	{ \
		kill(-curpid, SIGKILL); \
		kill(-curpid, SIGCONT); \
		while (wait(&retstat) != curpid); \
	} \
	while (0)
#endif
#else
#define TERM_TESTEE
#endif

#if defined HAVE_KILL && defined SIGINT
#define PROPAGATE_SIGINT ((void) kill(-curpid, SIGINT))
#else
#define PROPAGATE_SIGINT
#endif

struct child_stats
{
	int returncode;
	_PyTime_timeval walltime;
#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
	_PyTime_timeval cputime;
	Py_ssize_t memory;
#endif
};

static pid_t curpid;
static const struct child_stats zero_stats = { 0 };
static PyObject *CannotStartTestee, *CanceledByUser, *WallTimeLimitExceeded,
                *CPUTimeLimitExceeded, *MemoryLimitExceeded;
static _PyTime_timeval time_end;

#ifdef USE_WAKEUP_FD
static char dont_care_buffer[512];
static int intpipe[2] = { 0 };
#endif

#ifdef HAVE_TERMIOS_H
static bool catch_escape = false;
static struct termios orig_termios;
#endif

typedef struct
{
	PyObject_HEAD
	int returncode;
} _unix__PopenPlaceholderObject;

static PyMemberDef _PopenPlaceholder_members[] =
{
	{ "returncode", T_INT, offsetof(_unix__PopenPlaceholderObject, returncode), READONLY, NULL },
	{ NULL }
};

static PyTypeObject _unix__PopenPlaceholderType =
{
#if PY_MAJOR_VERSION >= 3
	PyVarObject_HEAD_INIT(NULL, 0)
#else
	PyObject_HEAD_INIT(NULL)
	0,                                        /*ob_size*/
#endif
	"_unix._PopenPlaceholder",                /*tp_name*/
	sizeof(_unix__PopenPlaceholderObject),    /*tp_basicsize*/
	0,                                        /*tp_itemsize*/
	0,                                        /*tp_dealloc*/
	0,                                        /*tp_print*/
	0,                                        /*tp_getattr*/
	0,                                        /*tp_setattr*/
	0,                                        /*tp_compare*/
	0,                                        /*tp_repr*/
	0,                                        /*tp_as_number*/
	0,                                        /*tp_as_sequence*/
	0,                                        /*tp_as_mapping*/
	0,                                        /*tp_hash */
	0,                                        /*tp_call*/
	0,                                        /*tp_str*/
	0,                                        /*tp_getattro*/
	0,                                        /*tp_setattro*/
	0,                                        /*tp_as_buffer*/
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
	0,                                        /*tp_doc*/
	0,                                        /*tp_traverse*/
	0,                                        /*tp_clear*/
	0,                                        /*tp_richcompare*/
	0,                                        /*tp_weaklistoffset*/
	0,                                        /*tp_iter*/
	0,                                        /*tp_iternext*/
	0,                                        /*tp_methods*/
	_PopenPlaceholder_members,                /*tp_members*/
};

#ifndef timeradd
#define timeradd(a, b, res) \
	do \
	{ \
		(res)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
		(res)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
		if ((res)->tv_usec >= 1000000) \
		{ \
			++(res)->tv_sec; \
			(res)->tv_usec -= 1000000; \
		} \
	} \
	while (0)
#endif

#ifndef timersub
#define timersub(a, b, res) \
	do \
	{ \
		(res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
		(res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
		if ((res)->tv_usec < 0) \
		{ \
			--(res)->tv_sec; \
			(res)->tv_usec += 1000000; \
		} \
	} \
	while (0)
#endif

#ifndef timerclear
#define timerclear(tvp) ((void) ((tvp)->tv_sec = (tvp)->tv_usec = 0))
#endif

#ifndef timerisset
#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
#endif

#ifndef timercmp
#define timercmp(a, b, cmp) \
	(((a)->tv_sec == (b)->tv_sec) \
		? ((a)->tv_usec cmp (b)->tv_usec) \
		: ((a)->tv_sec cmp (b)->tv_sec))
#endif

// Stolen from posixmodule.c of Python 2.7.1
static void free_string_array(char **array, Py_ssize_t count)
{
	Py_ssize_t i;
	for (i = 0; i < count; ++i)
		PyMem_Free(array[i]);
	PyMem_DEL(array);
}

// Stolen from termios.c of Python 2.7.1
static int fdconv(PyObject *obj, void *p)
{
	int fd = PyObject_AsFileDescriptor(obj);
	if (fd >= 0)
	{
		*((int *) p) = fd;
		return 1;
	}
	return 0;
}

// Parts stolen from bltinmodule.c, posixmodule.c and termios.c of Python 2.7.1
static int my_spawn(PyObject *args, PyObject *kwds, int c2ppipe[2], int maxcputime, Py_ssize_t maxmemory)
{
	static const char *const kwlist[] = { "stdin", "stdout", "stderr", NULL };
	static PyObject *dummy_args = NULL;
	Py_ssize_t i, argc;
	char **argv;
	bool own_args = false;
	int fdin = 0, fdout = 1, fderr = 2;
	
	if (dummy_args == NULL)
	{
		if (!(dummy_args = PyTuple_New(0)))
		{
			return -1;
		}
	}
	
	if (!PyArg_ParseTuple(args, "O:call", &args))
	{
		return -1;
	}
	if (!PyArg_ParseTupleAndKeywords(dummy_args, kwds, "|O&O&O&:call", (char **) kwlist, fdconv, &fdin, fdconv, &fdout, fdconv, &fderr))
	{
		return -1;
	}
	
#if PY_MAJOR_VERSION >= 3
	if (PyUnicode_Check(args))
#else
	if (PyString_Check(args) || PyUnicode_Check(args))
#endif
	{
		argc = 1;
		args = PyTuple_Pack(1, args);
		if (args == NULL)
		{
			return -1;
		}
		own_args = true;
	}
	else if (!PySequence_Check(args))
	{
		PyErr_SetString(PyExc_TypeError, "call() argument must be a sequence or string");
		return -1;
	}
	else
	{
		argc = PySequence_Size(args);
		if (argc < 1)
		{
			PyErr_SetString(PyExc_TypeError, "call() argument must not be empty");
			return -1;
		}
	}
	
	argv = PyMem_NEW(char *, argc + 1);
	if (argv == NULL)
	{
		if (own_args)
		{
			Py_DECREF(args);
		}
		PyErr_NoMemory();
		return -1;
	}
	
	for (i = 0; i < argc; ++i)
	{
		if (!PyArg_Parse(PySequence_ITEM(args, i), "et", Py_FileSystemDefaultEncoding, &argv[i]))
		{
			free_string_array(argv, i);
			if (own_args)
			{
				Py_DECREF(args);
			}
			PyErr_SetString(PyExc_TypeError, "call() argument must contain only strings");
			return -1;
		}
	}
	argv[argc] = NULL;
	
	curpid = fork();
	if (!curpid)
	{
		pid_t pid;
		int spawn_errno, status, fd, fddupped[3];
		struct child_stats stats;
		_PyTime_timeval tvstart, tvend;
#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
		struct rusage rusage;
#endif
#if defined RLIMIT_AS || defined RLIMIT_CPU
		struct rlimit rlimit;
#endif
		
		/*
		Assume no errors occur:
		* POSIX:2008 doesn't even define any errors for setpgrp,
		  nor does the (probably copied-verbatim-from-FreeBSD) man page
		  on Mac OS X 10.6;
		* none of the error conditions POSIX:2008 does define
		  for setpgid can occur.
		*/
#ifdef HAVE_SETPGID
		setpgid(0, 0);
#else //if defined HAVE_SETPGRP
#ifdef SETPGRP_HAVE_ARG
		setpgrp(0, 0);
#else
		setpgrp();
#endif
#endif
		
#ifdef SIGINT
		signal(SIGINT, SIG_DFL);
#endif
		
#if PY_MAJOR_VERSION > 3 || PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2
		_Py_RestoreSignals();
#else
#ifdef SIGPIPE
		signal(SIGPIPE, SIG_DFL);
#endif
#ifdef SIGXFSZ
		signal(SIGXFSZ, SIG_DFL);
#endif
#ifdef SIGXFZ
		signal(SIGXFZ, SIG_DFL);
#endif
#endif
		
		if (c2ppipe[1] < 3)
		{
			int newfd;
#ifdef F_DUPFD_CLOEXEC
			newfd = fcntl(c2ppipe[1], F_DUPFD_CLOEXEC, 3);
			if (newfd == -1)
			{
				spawn_errno = errno;
				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
				_exit(127);
			}
			c2ppipe[1] = newfd;
#else
			newfd = fcntl(c2ppipe[1], F_DUPFD, 3);
			// Other threads should not fork/spawn right now
			if (newfd == -1
			 || fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) | FD_CLOEXEC) == -1)
			{
				spawn_errno = errno;
				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
				_exit(127);
			}
			c2ppipe[1] = newfd;
#endif
		}
		// Yes, this works as intended even if fdin == fdout == fderr == 0
		// and there are no open file descriptors except 0 and c2ppipe
		// FIXME: error handling
		fddupped[0] = dup(fdin);
		fddupped[1] = dup(fdout);
		fddupped[2] = dup(fderr);
		dup2(fddupped[0], 0);
		dup2(fddupped[1], 1);
		dup2(fddupped[2], 2);
		// FIXME: close() may fail with EINTR or EIO; is setting CLOEXEC safer?
		// Bear in mind we still want to close them in _this_ process
		for (fd = sysconf(_SC_OPEN_MAX); --fd > c2ppipe[1]; )
		{
			close(fd);
		}
		while (--fd >= 3)
		{
			close(fd);
		}
		
#ifdef RLIMIT_AS
		if (maxmemory)
		{
			rlimit.rlim_cur = rlimit.rlim_max = maxmemory;
			setrlimit(RLIMIT_AS, &rlimit);
		}
#endif
#ifdef RLIMIT_CPU
		if (maxcputime)
		{
			rlimit.rlim_cur = rlimit.rlim_max = maxcputime;
			setrlimit(RLIMIT_CPU, &rlimit);
		}
#endif
		
#ifdef HAVE_SPAWN_H
#ifdef __APPLE__
		if (posix_spawnp != NULL)
		{
#endif
			spawn_errno = posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ);
			_PyTime_gettimeofday(&tvstart);
			
			if (spawn_errno)
			{
				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
				_exit(127);
			}
#ifdef __APPLE__
		}
		else
#endif
#endif
#if !(defined HAVE_SPAWN_H) || defined __APPLE__
		{
			pid = fork();
			if (!pid)
			{
				execvp(argv[0], argv);
				spawn_errno = errno;
				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
				_exit(127);
			}
			else if (pid == -1)
			{
				spawn_errno = errno;
				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
				_exit(127);
			}
			else
			{
				_PyTime_gettimeofday(&tvstart);
			}
		}
#endif
		TESTEE_REPORT_STATUS(TESTEE_SPAWNED);
		write(c2ppipe[1], &tvstart, sizeof tvstart);
		
#ifdef HAVE_WAIT4
		while (wait4(pid, &status, 0, &rusage) != pid);
#elif defined HAVE_WAIT3
		while (wait3(&status, 0, &rusage) != pid);
#elif defined HAVE_WAITPID
		while (waitpid(pid, &status, 0) != pid);
#else
		while (wait(&status) != pid);
#endif
		
		_PyTime_gettimeofday(&tvend);
#if defined HAVE_SYS_RESOURCE_H && !(defined HAVE_WAIT4 || defined HAVE_WAIT3)
		getrusage(RUSAGE_CHILDREN, &rusage);
#endif
		
		stats = zero_stats;
		
		if (WIFEXITED(status) && WEXITSTATUS(status) == 127) _exit(127);
		else if (WIFSIGNALED(status)) stats.returncode = -WTERMSIG(status);
		else stats.returncode = WEXITSTATUS(status);
		
		timersub(&tvend, &tvstart, &stats.walltime);
#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
		timeradd(&rusage.ru_utime, &rusage.ru_stime, &stats.cputime);
#ifdef __APPLE__
		stats.memory = rusage.ru_maxrss;
#else
		stats.memory = rusage.ru_maxrss << 10;
#endif
#endif
		
		write(c2ppipe[1], &stats, sizeof stats);
		_exit(0);
	}
	else if (curpid == -1)
	{
		PyErr_SetFromErrno(PyExc_OSError);
		free_string_array(argv, argc);
		if (own_args)
		{
			Py_DECREF(args);
		}
		return 0;
	}
	
	/*
	Assume no errors occur if the child is still alive:
	* the (probably copied-verbatim-from-FreeBSD) man page
	  on Mac OS X 10.6 doesn't even define any errors for setpgrp;
	* none of the error conditions POSIX:2008 defines
	  for setpgid can occur.
	*/
#ifdef HAVE_SETPGID
	setpgid(curpid, 0);
#elif defined SETPGRP_HAVE_ARG
	setpgrp(curpid, 0);
#endif
	
	free_string_array(argv, argc);
	if (own_args)
	{
		Py_DECREF(args);
	}
	return 1;
}

static inline bool attr_to_timeval(PyObject *obj, const char *attr, _PyTime_timeval *ptv)
{
#ifdef HAVE_LONG_LONG
	long long i_whole;
#else
	long i_whole;
#endif
	PyObject *whole, *frac, *million, *usec, *usec_whole;
	PyObject *member = PyObject_GetAttrString(obj, attr);
	if (member == NULL)
	{
		return false;
	}
	if (member == Py_None)
	{
		Py_DECREF(member);
		timerclear(ptv);
		return true;
	}
	whole = PyNumber_Int(member);
	if (whole == NULL)
	{
		Py_DECREF(member);
		return false;
	}
#ifdef HAVE_LONG_LONG
	i_whole = PyLong_AsLongLong(whole);
#else
	i_whole = PyInt_AsLong(whole);
#endif
	if (i_whole == -1 && PyErr_Occurred() != NULL)
	{
		Py_DECREF(whole);
		Py_DECREF(member);
		return false;
	}
	// FIXME: detect time_t overflow
	ptv->tv_sec = i_whole;
	frac = PyNumber_Subtract(member, whole);
	Py_DECREF(whole);
	Py_DECREF(member);
	if (frac == NULL)
	{
		return false;
	}
	million = PyInt_FromLong(1000000);
	if (million == NULL)
	{
		Py_DECREF(frac);
		return false;
	}
	usec = PyNumber_InPlaceMultiply(frac, million);
	Py_DECREF(million);
	Py_DECREF(frac);
	if (usec == NULL)
	{
		return false;
	}
	usec_whole = PyNumber_Int(usec);
	Py_DECREF(usec);
	if (usec_whole == NULL)
	{
		return false;
	}
	// FIXME: a sanity check (0 <= value < 1000000) here wouldn't harm
	ptv->tv_usec = PyInt_AsLong(usec_whole);
	Py_DECREF(usec_whole);
	return ptv->tv_usec != -1 || PyErr_Occurred() == NULL;
}

#ifdef __cplusplus
typedef struct { char a[2]; } two_chars;
static char is_int(char);
static char is_int(signed char);
static char is_int(unsigned char);
static char is_int(short);
static char is_int(unsigned short);
static char is_int(int);
static char is_int(unsigned);
static char is_int(long);
static char is_int(unsigned long);
#ifdef HAVE_LONG_LONG
static char is_int(long long);
static char is_int(unsigned long long);
#endif
static two_chars is_int(...);
#endif

static inline bool timeval_to_attr(_PyTime_timeval *ptv, PyObject *obj, const char *attr)
{
	PyObject *value;
#ifdef __cplusplus
	// If tv_sec has an integral type and !tv_usec, try to create a Python int
	if (sizeof is_int(ptv->tv_sec) == sizeof(char) && !ptv->tv_usec)
	{
		if (ptv->tv_sec <= LONG_MAX)
		{
			value = PyInt_FromLong(ptv->tv_sec);
		}
		// FIXME: signed/unsigned comparisons ruin everything
#ifdef HAVE_LONG_LONG
		else// if (ptv->tv_sec <= ULLONG_MAX)
		{
			value = PyLong_FromUnsignedLongLong(ptv->tv_sec);
		}
#else
//		else if (ptv->tv_sec <= ULONG_MAX)
//		{
//			value = PyLong_FromUnsignedLong(ptv->tv_sec);
//		}
//#endif
		else
		{
			value = PyFloat_FromDouble(ptv->tv_sec);
		}
//
#endif
//
	}
	else
#endif
	{
		// TODO: use decimal.Decimal or fractions.Fraction
		value = PyFloat_FromDouble(ptv->tv_sec + ptv->tv_usec * 0.000001);
	}
	if (value == NULL)
	{
		return false;
	}
	if (PyObject_SetAttrString(obj, attr, value) == -1)
	{
		return false;
	}
	Py_DECREF(value);
	return true;
}

/*
TODO/FIXME:
* Replace timeval->timespec and select->pselect if pselect is available
 (preferably only if pselect is not a wrapper around select).
* File descriptors might be >= FD_SETSIZE?
*/
static PyObject *_unix_call(PyObject *self, PyObject *args, PyObject *kwds)
{
	PyObject *testcase = NULL, *obj;
	_unix__PopenPlaceholderObject *Popen_placeholder;
	int spawn_errno = 0, spawn_status, s, c2ppipe[2], retstat;
	struct child_stats stats = zero_stats;
	_PyTime_timeval maxwalltime, maxcputime, timeout, time_start;
	Py_ssize_t maxmemory, r;
	size_t stats_read = 0;
	fd_set readfds;
	char c;
	bool have_maxwalltime;
	
	if (kwds != NULL)
	{
		testcase = PyDict_GetItemString(kwds, "case");
	}
	if (testcase == NULL)
	{
		PyErr_SetString(PyExc_TypeError, "call() requires a keyword argument 'case'");
		return NULL;
	}
	Py_INCREF(testcase);
	PyDict_DelItemString(kwds, "case");
	
	if (!attr_to_timeval(testcase, "maxwalltime", &maxwalltime)
	 || !attr_to_timeval(testcase, "maxcputime", &maxcputime))
	{
		Py_DECREF(testcase);
		return NULL;
	}
	
	obj = PyObject_GetAttrString(testcase, "maxmemory");
	if (obj == NULL)
	{
		Py_DECREF(testcase);
		return NULL;
	}
	if (PyObject_IsTrue(obj))
	{
		PyObject *factor, *bytes;
		factor = PyInt_FromLong(1024 * 1024);
		if (factor == NULL)
		{
			Py_DECREF(testcase);
			return NULL;
		}
		bytes = PyNumber_Multiply(obj, factor);
		Py_DECREF(factor);
		if (bytes == NULL)
		{
			Py_DECREF(testcase);
			return NULL;
		}
		maxmemory = PyNumber_AsSsize_t(bytes, PyExc_OverflowError);
		Py_DECREF(bytes);
		if (maxmemory == -1 && PyErr_Occurred() != NULL)
		{
			Py_DECREF(testcase);
			return NULL;
		}
	}
	else
	{
		maxmemory = 0;
	}
	Py_DECREF(obj);
	
#ifdef HAVE_PIPE2
	if (pipe2(c2ppipe, O_CLOEXEC))
	{
		PyErr_SetFromErrno(PyExc_IOError);
		Py_DECREF(testcase);
		return NULL;
	}
#else
	if (pipe(c2ppipe))
	{
		PyErr_SetFromErrno(PyExc_IOError);
		Py_DECREF(testcase);
		return NULL;
	}
	// Does any other thread fork/spawn right now? Please shoot it in the head
	// (well, if this ends up causing trouble, anyway)
	if (fcntl(c2ppipe[0], F_SETFD, fcntl(c2ppipe[0], F_GETFD) | FD_CLOEXEC) == -1
	 || fcntl(c2ppipe[1], F_SETFD, fcntl(c2ppipe[1], F_GETFD) | FD_CLOEXEC) == -1)
	{
		PyErr_SetFromErrno(PyExc_IOError);
		close(c2ppipe[0]);
		close(c2ppipe[1]);
		Py_DECREF(testcase);
		return NULL;
	}
#endif
	
	spawn_status = my_spawn(args, kwds, c2ppipe, maxcputime.tv_sec + (maxcputime.tv_usec > 0), maxmemory);
	close(c2ppipe[1]);
	if (!spawn_status)
	{
		PyObject *type, *value, *traceback, *e;
		close(c2ppipe[0]);
		Py_DECREF(testcase);
		PyErr_Fetch(&type, &value, &traceback);
		PyErr_NormalizeException(&type, &value, &traceback);
		Py_XDECREF(traceback);
		Py_DECREF(type);
		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
		Py_DECREF(value);
		PyErr_SetObject(CannotStartTestee, e);
		Py_DECREF(e);
		return NULL;
	}
	else if (spawn_status < 0)
	{
		close(c2ppipe[0]);
		Py_DECREF(testcase);
		return NULL;
	}
	
	// FIXME: use select in order not to miss SIGINT
	while ((r = read(c2ppipe[0], &c, 1)) == -1 && errno == EINTR)
	{
		if (PyErr_CheckSignals() == -1)
		{
			PROPAGATE_SIGINT;
			close(c2ppipe[0]);
			Py_DECREF(testcase);
			TERM_TESTEE;
			return NULL;
		}
	}
	if (r == 1)
	{
		if (c == TESTEE_SPAWNED)
		{
			size_t got = 0;
			while (got < sizeof time_start)
			{
				r = read(c2ppipe[0], got + (char *) &time_start, sizeof time_start - got);
				if (r > 0)
				{
					got += r;
				}
				else if (!r)
				{
					errno = 0;
					PyErr_SetFromErrno(PyExc_IOError);
					goto spawn_failed;
				}
				else if (errno == EINTR)
				{
					if (PyErr_CheckSignals() == -1)
					{
						PROPAGATE_SIGINT;
						close(c2ppipe[0]);
						Py_DECREF(testcase);
						TERM_TESTEE;
						return NULL;
					}
				}
				else
				{
					PyErr_SetFromErrno(PyExc_IOError);
					goto spawn_failed;
				}
			}
			if (!timeval_to_attr(&time_start, testcase, "time_started"))
			{
				close(c2ppipe[0]);
				Py_DECREF(testcase);
				TERM_TESTEE;
				return NULL;
			}
		}
		else // if (c == TESTEE_SPAWN_FAILED)
		{
			size_t got = 0;
			while (got < sizeof spawn_errno)
			{
				r = read(c2ppipe[0], got + (char *) &spawn_errno, sizeof spawn_errno - got);
				if (r > 0)
				{
					got += r;
				}
				else if (!r)
				{
					// Can't get the real error; use zero instead
					spawn_errno = 0;
					break;
				}
				else if (errno == EINTR)
				{
					if (PyErr_CheckSignals() == -1)
					{
						PROPAGATE_SIGINT;
						close(c2ppipe[0]);
						Py_DECREF(testcase);
						TERM_TESTEE;
						return NULL;
					}
				}
				else
				{
					PyErr_SetFromErrno(PyExc_IOError);
					goto spawn_failed;
				}
			}
			errno = spawn_errno;
			/*
			if (errno == EACCES || errno == EINVAL || errno == ELOOP
			 || errno == ENAMETOOLONG || errno == ENOENT || errno == ENOTDIR
			 || errno == ENOEXEC || errno == ETXTBSY)
			{
				PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, PySequence_ITEM(args, 0));
			}
			else
			{*/
				PyErr_SetFromErrno(PyExc_OSError);
			//}
			goto spawn_failed;
		}
	}
	else
	{
		PyObject *type, *value, *traceback, *e;
		if (!r) errno = 0;
		PyErr_SetFromErrno(PyExc_IOError);
spawn_failed:
		Py_DECREF(testcase);
		close(c2ppipe[0]);
		TERM_TESTEE;
		PyErr_Fetch(&type, &value, &traceback);
		PyErr_NormalizeException(&type, &value, &traceback);
		Py_XDECREF(traceback);
		Py_DECREF(type);
		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
		Py_DECREF(value);
		PyErr_SetObject(CannotStartTestee, e);
		Py_DECREF(e);
		return NULL;
	}
	
	Py_BEGIN_ALLOW_THREADS
	timeradd(&time_start, &maxwalltime, &time_end);
	FD_ZERO(&readfds);
	have_maxwalltime = timerisset(&maxwalltime);
	/*
	Implementations may place limitations on the maximum timeout
	interval supported. All implementations shall support a maximum
	timeout interval of at least 31 days. If the timeout argument
	specifies a timeout interval greater than the implementation-
	defined maximum value, the maximum value shall be used as the
	actual timeout value.
		(POSIX:2008)
	Therefore the loop and the && timercmp(&time_end, &now, <).
	*/
	for (;;)
	{
		_PyTime_timeval now;
		int maxfd = c2ppipe[0];
#ifdef HAVE_TERMIOS_H
		if (catch_escape) FD_SET(0, &readfds);
#endif
#ifdef USE_WAKEUP_FD
		FD_SET(intpipe[0], &readfds);
		if (intpipe[0] > maxfd) maxfd = intpipe[0];
#endif
		FD_SET(c2ppipe[0], &readfds);
		
		if (have_maxwalltime)
		{
			_PyTime_gettimeofday(&now);
			if (timercmp(&time_end, &now, <))
			{
				timerclear(&timeout);
			}
			else
			{
				timersub(&time_end, &now, &timeout);
			}
			
			s = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
			
			if (!s && timercmp(&time_end, &now, <))
			{
				close(c2ppipe[0]);
				TERM_TESTEE;
				Py_BLOCK_THREADS
				Py_DECREF(testcase);
				PyErr_SetObject(WallTimeLimitExceeded, NULL);
				return NULL;
			}
		}
		else
		{
			s = select(maxfd + 1, &readfds, NULL, NULL, NULL);
		}
		
		if (s < 0 && errno == EINTR)
		{
			Py_BLOCK_THREADS
			if (PyErr_CheckSignals() == -1)
			{
				PROPAGATE_SIGINT;
				close(c2ppipe[0]);
				Py_DECREF(testcase);
				TERM_TESTEE;
				return NULL;
			}
			Py_UNBLOCK_THREADS
		}
		else if (s < 0 && errno != EAGAIN)
		{
			Py_BLOCK_THREADS
			PyErr_SetFromErrno(PyExc_IOError);
			close(c2ppipe[0]);
			Py_DECREF(testcase);
			TERM_TESTEE;
			return NULL;
		}
#ifdef USE_WAKEUP_FD
		else if (s > 0 && FD_ISSET(intpipe[0], &readfds))
		{
			// FIXME: is error handling needed?
			while (read(intpipe[0], dont_care_buffer, sizeof dont_care_buffer) > 0);
			Py_BLOCK_THREADS
			if (PyErr_CheckSignals() == -1)
			{
				PROPAGATE_SIGINT;
				close(c2ppipe[0]);
				Py_DECREF(testcase);
				TERM_TESTEE;
				return NULL;
			}
			Py_UNBLOCK_THREADS
		}
#endif
#ifdef HAVE_TERMIOS_H
		else if (s > 0 && !FD_ISSET(c2ppipe[0], &readfds))
		{
			// FIXME: is error and EOF handling needed?
			if ((r = read(0, &c, 1)) == 1)
			{
				if (c == '\33')
				{
					close(c2ppipe[0]);
					TERM_TESTEE;
					Py_BLOCK_THREADS
					Py_DECREF(testcase);
					PyErr_SetObject(CanceledByUser, NULL);
					return NULL;
				}
			}
			else if (r == -1 && errno == EINTR)
			{
				if (PyErr_CheckSignals() == -1)
				{
					PROPAGATE_SIGINT;
					close(c2ppipe[0]);
					Py_DECREF(testcase);
					TERM_TESTEE;
					return NULL;
				}
			}
		}
#endif
		else if (s > 0)
		{
			bool blocked_threads = false;
			while ((r = read(c2ppipe[0], stats_read + (char *) &stats, sizeof stats - stats_read)) == -1 && errno == EINTR)
			{
				Py_BLOCK_THREADS
				blocked_threads = true;
				if (PyErr_CheckSignals() == -1)
				{
					PROPAGATE_SIGINT;
					close(c2ppipe[0]);
					Py_DECREF(testcase);
					TERM_TESTEE;
					return NULL;
				}
			}
			if (r > 0)
			{
				stats_read += r;
			}
			else if (!r)
			{
				break;
			}
			else
			{
				close(c2ppipe[0]);
				TERM_TESTEE;
				if (!blocked_threads)
				{
					Py_BLOCK_THREADS
				}
				Py_DECREF(testcase);
				PyErr_SetFromErrno(PyExc_IOError);
				return NULL;
			}
			if (blocked_threads)
			{
				Py_UNBLOCK_THREADS
			}
		}
	}
	close(c2ppipe[0]);
	Py_END_ALLOW_THREADS
	
#ifdef HAVE_WAITPID
	while (waitpid(curpid, &retstat, 0) != curpid)
#else
	while (wait(&retstat) != curpid)
#endif
	{
		if (PyErr_CheckSignals() == -1)
		{
			Py_DECREF(testcase);
			return NULL;
		}
	}
	
	if (WIFEXITED(retstat) && WEXITSTATUS(retstat) == 127)
	{
		PyObject *type, *value, *traceback, *e;
		Py_DECREF(testcase);
		errno = 0;
		PyErr_SetFromErrno(PyExc_OSError);
		PyErr_Fetch(&type, &value, &traceback);
		PyErr_NormalizeException(&type, &value, &traceback);
		Py_XDECREF(traceback);
		Py_DECREF(type);
		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
		Py_DECREF(value);
		PyErr_SetObject(CannotStartTestee, e);
		Py_DECREF(e);
		return NULL;
	}
	else if (!WIFEXITED(retstat) || WEXITSTATUS(retstat))
	{
		Py_DECREF(testcase);
		if (WIFSTOPPED(retstat))
		{
			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: stopped by signal %d", WSTOPSIG(retstat));
		}
		else if (WIFSIGNALED(retstat))
		{
			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: terminated by signal %d", WTERMSIG(retstat));
		}
		else if (WIFEXITED(retstat))
		{
			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: %d", WEXITSTATUS(retstat));
		}
		else
		{
			PyErr_SetString(PyExc_EnvironmentError, "unexpected exit status from worker: not exited, signaled or stopped");
			return NULL;
		}
	}
	
	if (stats_read != sizeof stats)
	{
		Py_DECREF(testcase);
		PyErr_SetString(PyExc_EnvironmentError, "unexpectedly early end of output from worker");
		return NULL;
	}
	
	if (timerisset(&maxwalltime) && timercmp(&stats.walltime, &maxwalltime, >))
	{
		Py_DECREF(testcase);
		PyErr_SetObject(WallTimeLimitExceeded, NULL);
		return NULL;
	}
	
	obj = PyInt_FromLong(0);
	if (obj == NULL)
	{
		Py_DECREF(testcase);
		return NULL;
	}
	if (PyObject_SetAttrString(testcase, "time_started", obj) == -1)
	{
		Py_DECREF(testcase);
		return NULL;
	}
	Py_DECREF(obj);
	
#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
	if (timerisset(&maxcputime) || !timerisset(&maxwalltime))
	{
		PyObject *cputls;
		if (!timeval_to_attr(&stats.cputime, testcase, "time_stopped"))
		{
			Py_DECREF(testcase);
			return NULL;
		}
		cputls = PyObject_GetAttrString(testcase, "cpu_time_limit_string");
		if (cputls == NULL)
		{
			Py_DECREF(testcase);
			return NULL;
		}
		if (PyObject_SetAttrString(testcase, "time_limit_string", cputls) == -1)
		{
			Py_DECREF(testcase);
			return NULL;
		}
		Py_DECREF(cputls);
		if (timerisset(&maxcputime) && timercmp(&stats.cputime, &maxcputime, >))
		{
			Py_DECREF(testcase);
			PyErr_SetObject(CPUTimeLimitExceeded, NULL);
			return NULL;
		}
	}
	else
#endif
	{
		if (!timeval_to_attr(&stats.walltime, testcase, "time_stopped"))
		{
			Py_DECREF(testcase);
			return NULL;
		}
	}
	
#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
	if (maxmemory && stats.memory > maxmemory)
	{
		Py_DECREF(testcase);
		PyErr_SetObject(MemoryLimitExceeded, NULL);
		return NULL;
	}
#endif
	
	Popen_placeholder = PyObject_New(_unix__PopenPlaceholderObject, &_unix__PopenPlaceholderType);
	if (Popen_placeholder == NULL)
	{
		return NULL;
	}
	Popen_placeholder->returncode = stats.returncode;
	PyObject_SetAttrString(testcase, "process", (PyObject *) Popen_placeholder);
	Py_DECREF(Popen_placeholder);
	Py_DECREF(testcase);
	Py_RETURN_NONE;
}

static PyObject *_unix_pause(PyObject *self)
{
#ifdef HAVE_TERMIOS_H
	if (catch_escape)
	{
		char c;
		while (read(0, &c, 1) == -1 && errno == EINTR);
	}
#endif
	Py_RETURN_NONE;
}

static PyMethodDef _unixMethods[] =
{
	{ "call", (PyCFunction) _unix_call, METH_VARARGS | METH_KEYWORDS, "Call a process." },
	{ "pause", (PyCFunction) _unix_pause, METH_NOARGS, "Block until a key is pressed." },
	{ NULL }
};

#ifdef USE_WAKEUP_FD
static void close_intpipe(void)
{
	close(intpipe[0]);
	close(intpipe[1]);
	intpipe[0] = intpipe[1] = 0;
	PySignal_SetWakeupFd(-1);
}
#endif

#ifdef HAVE_TERMIOS_H
static void restore_termios(void)
{
	tcsetattr(0, TCSAFLUSH, &orig_termios);
#ifdef USE_WAKEUP_FD
	close_intpipe();
#endif
}
#endif

#if PY_MAJOR_VERSION >= 3
#define INIT_FAIL return NULL
static PyModuleDef _unixmodule =
{
	PyModuleDef_HEAD_INIT,
	"_unix",
	NULL,
	-1,
	_unixMethods
};

PyMODINIT_FUNC PyInit__unix(void)
#else
#define INIT_FAIL return
PyMODINIT_FUNC init_unix(void)
#endif
{
#ifdef HAVE_TERMIOS_H
	struct termios new_termios;
#endif
	PyObject *exceptions, *module;
	
	_unix__PopenPlaceholderType.tp_new = PyType_GenericNew;
	if (PyType_Ready(&_unix__PopenPlaceholderType) == -1)
	{
		INIT_FAIL;
	}
	
	exceptions = PyImport_ImportModule("upreckon.exceptions");
	if (exceptions == NULL
	 || (CannotStartTestee = PyObject_GetAttrString(exceptions, "CannotStartTestee")) == NULL
	 || (CanceledByUser = PyObject_GetAttrString(exceptions, "CanceledByUser")) == NULL
	 || (WallTimeLimitExceeded = PyObject_GetAttrString(exceptions, "WallTimeLimitExceeded")) == NULL
	 || (CPUTimeLimitExceeded = PyObject_GetAttrString(exceptions, "CPUTimeLimitExceeded")) == NULL
	 || (MemoryLimitExceeded = PyObject_GetAttrString(exceptions, "MemoryLimitExceeded")) == NULL)
	{
		Py_XDECREF(MemoryLimitExceeded);
		Py_XDECREF(CPUTimeLimitExceeded);
		Py_XDECREF(WallTimeLimitExceeded);
		Py_XDECREF(CanceledByUser);
		Py_XDECREF(CannotStartTestee);
		Py_XDECREF(exceptions);
		INIT_FAIL;
	}
	Py_DECREF(exceptions);
	
#ifdef WITH_NEXT_FRAMEWORK
	if (environ == NULL)
	{
		environ = *_NSGetEnviron();
	}
#endif
	
#ifdef USE_WAKEUP_FD
	if (!intpipe[0] || !intpipe[1])
	{
#ifdef HAVE_PIPE2
		if (pipe2(intpipe, O_CLOEXEC | O_NONBLOCK))
		{
			PyErr_SetFromErrno(PyExc_IOError);
			Py_DECREF(MemoryLimitExceeded);
			Py_DECREF(CPUTimeLimitExceeded);
			Py_DECREF(WallTimeLimitExceeded);
			Py_DECREF(CanceledByUser);
			Py_DECREF(CannotStartTestee);
			INIT_FAIL;
		}
#else
		if (pipe(intpipe))
		{
			PyErr_SetFromErrno(PyExc_IOError);
			Py_DECREF(MemoryLimitExceeded);
			Py_DECREF(CPUTimeLimitExceeded);
			Py_DECREF(WallTimeLimitExceeded);
			Py_DECREF(CanceledByUser);
			Py_DECREF(CannotStartTestee);
			INIT_FAIL;
		}
		// Other threads must not fork now
		if (fcntl(intpipe[0], F_SETFD, fcntl(intpipe[0], F_GETFD) | FD_CLOEXEC) == -1
		 || fcntl(intpipe[1], F_SETFD, fcntl(intpipe[1], F_GETFD) | FD_CLOEXEC) == -1
		 || fcntl(intpipe[0], F_SETFL, fcntl(intpipe[0], F_GETFL) | O_NONBLOCK) == -1
		 || fcntl(intpipe[1], F_SETFL, fcntl(intpipe[1], F_GETFL) | O_NONBLOCK) == -1)
		{
			PyErr_SetFromErrno(PyExc_IOError);
			close(intpipe[0]);
			close(intpipe[1]);
			Py_DECREF(MemoryLimitExceeded);
			Py_DECREF(CPUTimeLimitExceeded);
			Py_DECREF(WallTimeLimitExceeded);
			Py_DECREF(CanceledByUser);
			Py_DECREF(CannotStartTestee);
			INIT_FAIL;
		}
#endif
	}
#endif
	
#if PY_MAJOR_VERSION >= 3
	module = PyModule_Create(&_unixmodule);
#else
	module = Py_InitModule("_unix", _unixMethods);
#endif
	if (module == NULL)
	{
#ifdef USE_WAKEUP_FD
		close(intpipe[0]);
		close(intpipe[1]);
#endif
		Py_DECREF(MemoryLimitExceeded);
		Py_DECREF(CPUTimeLimitExceeded);
		Py_DECREF(WallTimeLimitExceeded);
		Py_DECREF(CanceledByUser);
		Py_DECREF(CannotStartTestee);
		INIT_FAIL;
	}
	
#ifdef HAVE_TERMIOS_H
	if (!tcgetattr(0, &orig_termios))
	{
		new_termios = orig_termios;
		// Stolen from tty.py of Python 2.7.1
		new_termios.c_lflag &= ~(ECHO | ICANON);
		new_termios.c_cc[VMIN] = 1;
		new_termios.c_cc[VTIME] = 0;
		if (!Py_AtExit(restore_termios) && !tcsetattr(0, TCSAFLUSH, &new_termios))
		{
			catch_escape = true;
		}
	}
#ifdef USE_WAKEUP_FD
	else
	{
		Py_AtExit(close_intpipe);
	}
#endif
#elif defined USE_WAKEUP_FD
	Py_AtExit(close_intpipe);
#endif
	
#ifdef USE_WAKEUP_FD
	PySignal_SetWakeupFd(intpipe[1]);
#endif
	
#if PY_MAJOR_VERSION >= 3
	return module;
#endif
}