changeset 136:ed4035661b85

Added a C implementation of the unix module (called _unix)
author Oleg Oshmyan <chortos@inbox.lv>
date Tue, 24 May 2011 20:51:01 +0100
parents 523ba6907f3a
children f4361d557929
files _unixmodule.cpp testcases.py unix.py
diffstat 3 files changed, 1481 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/_unixmodule.cpp	Tue May 24 20:51:01 2011 +0100
@@ -0,0 +1,1448 @@
+// Copyright (c) 2011 Chortos-2 <chortos@inbox.lv>
+
+#include <Python.h>
+#include <structmember.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 *testcases;
+	
+	_unix__PopenPlaceholderType.tp_new = PyType_GenericNew;
+	if (PyType_Ready(&_unix__PopenPlaceholderType) == -1)
+	{
+		INIT_FAIL;
+	}
+	
+	testcases = PyImport_ImportModule("testcases");
+	if (testcases == NULL)
+	{
+		INIT_FAIL;
+	}
+	if ((CannotStartTestee = PyObject_GetAttrString(testcases, "CannotStartTestee")) == NULL
+	 || (CanceledByUser = PyObject_GetAttrString(testcases, "CanceledByUser")) == NULL
+	 || (WallTimeLimitExceeded = PyObject_GetAttrString(testcases, "WallTimeLimitExceeded")) == NULL
+	 || (CPUTimeLimitExceeded = PyObject_GetAttrString(testcases, "CPUTimeLimitExceeded")) == NULL
+	 || (MemoryLimitExceeded = PyObject_GetAttrString(testcases, "MemoryLimitExceeded")) == NULL)
+	{
+		Py_XDECREF(MemoryLimitExceeded);
+		Py_XDECREF(CPUTimeLimitExceeded);
+		Py_XDECREF(WallTimeLimitExceeded);
+		Py_XDECREF(CanceledByUser);
+		Py_XDECREF(CannotStartTestee);
+		Py_DECREF(testcases);
+		INIT_FAIL;
+	}
+	Py_DECREF(testcases);
+	
+#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
+	
+#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
+	PyObject *module = PyModule_Create(&_unixmodule);
+	if (module == NULL)
+	{
+		Py_DECREF(MemoryLimitExceeded);
+		Py_DECREF(CPUTimeLimitExceeded);
+		Py_DECREF(WallTimeLimitExceeded);
+		Py_DECREF(CanceledByUser);
+		Py_DECREF(CannotStartTestee);
+	}
+	return module;
+#else
+	Py_InitModule("_unix", _unixMethods);
+#endif
+}
--- a/testcases.py	Tue May 24 18:59:00 2011 +0100
+++ b/testcases.py	Tue May 24 20:51:01 2011 +0100
@@ -34,10 +34,7 @@
 	def __exit__(self, exc_type, exc_value, traceback): pass
 signal_ignorer = DummySignalIgnorer()
 
-try:
-	from win32 import *
-except Exception:
-	from unix import *
+# win32 and unix are imported a bit later
 
 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed',
            'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer',
@@ -80,6 +77,12 @@
 class CannotReadInputFile(ExceptionWrapper): __slots__ = ()
 class CannotReadAnswerFile(ExceptionWrapper): __slots__ = ()
 
+# Import platform-specific code now that exception classes are defined
+try:
+	from win32 import *
+except Exception:
+	from unix import *
+
 
 
 # Helper context managers
@@ -185,6 +188,8 @@
 			case.cleanup()
 	
 	def cleanup(case):
+		# Note that native extensions clean up on their own
+		# and never let this condition be satisfied
 		if getattr(case, 'process', None) and case.process.returncode is None:
 			kill(case.process)
 		for name in case.files_to_delete:
--- a/unix.py	Tue May 24 18:59:00 2011 +0100
+++ b/unix.py	Tue May 24 20:51:01 2011 +0100
@@ -25,25 +25,6 @@
 __all__ = 'call', 'kill', 'pause', 'clock'
 
 
-if not sys.stdin.isatty():
-	pause = lambda: sys.stdin.read(1)
-	catch_escape = False
-else:
-	try:
-		from select import select
-		import termios, tty, atexit
-	except ImportError:
-		pause = None
-		catch_escape = False
-	else:
-		catch_escape = True
-		def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
-			termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
-		atexit.register(cleanup)
-		tty.setcbreak(sys.stdin.fileno())
-		def pause():
-			sys.stdin.read(1)
-
 try:
 	from signal import SIGCHLD, SIG_DFL, signal, set_wakeup_fd
 	from select import select, error as SelectError
@@ -306,4 +287,27 @@
 				return process.wait()
 			except OSError:
 				if sys.exc_info()[1].errno != EINTR:
-					raise
\ No newline at end of file
+					raise
+
+
+try:
+	from _unix import *
+except ImportError:
+	if not sys.stdin.isatty():
+		pause = lambda: sys.stdin.read(1)
+		catch_escape = False
+	else:
+		try:
+			from select import select
+			import termios, tty, atexit
+		except ImportError:
+			pause = lambda: sys.stdin.read(1)
+			catch_escape = False
+		else:
+			catch_escape = True
+			def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
+				termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
+			atexit.register(cleanup)
+			tty.setcbreak(sys.stdin.fileno())
+			def pause():
+				sys.stdin.read(1)