view upreckon/_unixmodule.cpp @ 186:0bd782fd41dc

Fixed a possible compilation error introduced in the last changeset
author Oleg Oshmyan <chortos@inbox.lv>
date Tue, 26 Jul 2011 20:03:48 +0300
parents 9ed34ef740a1
children 3ab18984e3e3
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;
}
#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
{
	struct termios new_termios;
	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
	}
	
	PySignal_SetWakeupFd(intpipe[1]);
#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
	
#if PY_MAJOR_VERSION >= 3
	return module;
#endif
}