# HG changeset patch # User Oleg Oshmyan # Date 1306266661 -3600 # Node ID ed4035661b853028314835d84f1065c4d0201588 # Parent 523ba6907f3aea03301b0919b29c830ab0e5c910 Added a C implementation of the unix module (called _unix) diff -r 523ba6907f3a -r ed4035661b85 _unixmodule.cpp --- /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 + +#include +#include + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include + +#ifdef HAVE_SIGNAL_H +#include +#endif + +#ifdef HAVE_SPAWN_H +#include +#ifdef __APPLE__ +#pragma weak_import posix_spawnp +#endif +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifdef HAVE_TERMIOS_H +#include +#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 +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 +} diff -r 523ba6907f3a -r ed4035661b85 testcases.py --- 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: diff -r 523ba6907f3a -r ed4035661b85 unix.py --- 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)