Mercurial > ~astiob > upreckon > hgweb
view upreckon/_unixmodule.cpp @ 205:166a23999bf7
Added confvar okexitcodemask; changed the validator protocol
Callable validators now return three-tuples (number granted, bool correct,
str comment) instead of two-tuples (number granted, str comment). They are
still allowed to return single numbers.
Callable validators must now explicitly raise
upreckon.exceptions.WrongAnswer if they want the verdict to be Wrong
Answer rather than Partly Correct.
okexitcodemask specifies a bitmask ANDed with the exit code of the
external validator to get a boolean flag showing whether the answer is to
be marked as 'OK' rather than 'partly correct'. The bits covered by the
bitmask are reset to zeroes before devising the number of points granted
from the resulting number.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Wed, 17 Aug 2011 20:44:54 +0300 |
parents | 4f69e30abbd5 |
children | 8c4e92fb32d8 |
line wrap: on
line source
// Copyright (c) 2011 Chortos-2 <chortos@inbox.lv> #include <Python.h> #include <structmember.h> #include <stdio.h> #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> #endif #ifdef HAVE_FCNTL_H #include <fcntl.h> #endif #include <limits.h> #ifdef HAVE_SIGNAL_H #include <signal.h> #endif #ifdef HAVE_SPAWN_H #include <spawn.h> #ifdef __APPLE__ #pragma weak_import posix_spawnp #endif #endif #ifdef HAVE_SYS_RESOURCE_H #include <sys/resource.h> #endif #ifdef HAVE_SYS_WAIT_H #include <sys/wait.h> #endif #ifdef HAVE_TERMIOS_H #include <termios.h> #endif #if !(defined __cplusplus) && !(defined bool) #ifdef HAVE_C99_BOOL #define bool _Bool #else #define bool char #endif #undef true #define true 1 #undef false #define false 0 #endif // On Python 2.5, SIGINT handling may get delayed until we return to Python #if PY_MAJOR_VERSION > 2 || PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 #define USE_WAKEUP_FD #endif #if !(defined RLIMIT_AS) && defined RLIMIT_VMEM #define RLIMIT_AS RLIMIT_VMEM #endif // Condition stolen from posixmodule.c of Python 2.7.1 #if defined __USLC__ && defined __SCO_VERSION__ // SCO UDK Compiler //#ifdef HAVE_FORK1 #define fork fork1 #endif // Stolen from posixmodule.c of Python 2.7.1 #ifdef WITH_NEXT_FRAMEWORK #include <crt_externs.h> static char **environ = NULL; #elif !(defined _MSC_VER) && (!(defined __WATCOMC__) || defined __QNX__) extern char **environ; #endif #ifndef Py_PYTIME_H typedef struct timeval _PyTime_timeval; #ifndef GETTIMEOFDAY_NO_TZ #define _PyTime_gettimeofday(tvp) gettimeofday((tvp), NULL) #else #define _PyTime_gettimeofday(tvp) gettimeofday((tvp)) #endif #endif #if PY_MAJOR_VERSION >= 3 #define PyInt_AsLong PyLong_AsLong #define PyInt_FromLong PyLong_FromLong #define PyNumber_Int PyNumber_Long #endif #define TESTEE_SPAWNED 0 #define TESTEE_SPAWN_FAILED 1 #define TESTEE_REPORT_STATUS(status) \ do \ { \ const char c = (status); \ write(c2ppipe[1], &c, 1); \ } \ while (0) #if !(defined SIGKILL) && defined SIGTERM #define SIGKILL SIGTERM #endif #if defined HAVE_KILL && defined SIGKILL #ifdef HAVE_WAITPID #define TERM_TESTEE \ do \ { \ kill(-curpid, SIGKILL); \ kill(-curpid, SIGCONT); \ while (waitpid(curpid, &retstat, 0) != curpid); \ } \ while (0) #else #define TERM_TESTEE \ do \ { \ kill(-curpid, SIGKILL); \ kill(-curpid, SIGCONT); \ while (wait(&retstat) != curpid); \ } \ while (0) #endif #else #define TERM_TESTEE #endif #if defined HAVE_KILL && defined SIGINT #define PROPAGATE_SIGINT ((void) kill(-curpid, SIGINT)) #else #define PROPAGATE_SIGINT #endif struct child_stats { int returncode; _PyTime_timeval walltime; #if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3 _PyTime_timeval cputime; Py_ssize_t memory; #endif }; static pid_t curpid; static const struct child_stats zero_stats = { 0 }; static PyObject *CannotStartTestee, *CanceledByUser, *WallTimeLimitExceeded, *CPUTimeLimitExceeded, *MemoryLimitExceeded; static _PyTime_timeval time_end; #ifdef USE_WAKEUP_FD static char dont_care_buffer[512]; static int intpipe[2] = { 0 }; #endif #ifdef HAVE_TERMIOS_H static bool catch_escape = false; static struct termios orig_termios; #endif typedef struct { PyObject_HEAD int returncode; } _unix__PopenPlaceholderObject; static PyMemberDef _PopenPlaceholder_members[] = { { "returncode", T_INT, offsetof(_unix__PopenPlaceholderObject, returncode), READONLY, NULL }, { NULL } }; static PyTypeObject _unix__PopenPlaceholderType = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ #endif "_unix._PopenPlaceholder", /*tp_name*/ sizeof(_unix__PopenPlaceholderObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ 0, /*tp_methods*/ _PopenPlaceholder_members, /*tp_members*/ }; #ifndef timeradd #define timeradd(a, b, res) \ do \ { \ (res)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ (res)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ if ((res)->tv_usec >= 1000000) \ { \ ++(res)->tv_sec; \ (res)->tv_usec -= 1000000; \ } \ } \ while (0) #endif #ifndef timersub #define timersub(a, b, res) \ do \ { \ (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ if ((res)->tv_usec < 0) \ { \ --(res)->tv_sec; \ (res)->tv_usec += 1000000; \ } \ } \ while (0) #endif #ifndef timerclear #define timerclear(tvp) ((void) ((tvp)->tv_sec = (tvp)->tv_usec = 0)) #endif #ifndef timerisset #define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) #endif #ifndef timercmp #define timercmp(a, b, cmp) \ (((a)->tv_sec == (b)->tv_sec) \ ? ((a)->tv_usec cmp (b)->tv_usec) \ : ((a)->tv_sec cmp (b)->tv_sec)) #endif // Stolen from posixmodule.c of Python 2.7.1 static void free_string_array(char **array, Py_ssize_t count) { Py_ssize_t i; for (i = 0; i < count; ++i) PyMem_Free(array[i]); PyMem_DEL(array); } // Stolen from termios.c of Python 2.7.1 static int fdconv(PyObject *obj, void *p) { int fd = PyObject_AsFileDescriptor(obj); if (fd >= 0) { *((int *) p) = fd; return 1; } return 0; } // Parts stolen from bltinmodule.c, posixmodule.c and termios.c of Python 2.7.1 static int my_spawn(PyObject *args, PyObject *kwds, int c2ppipe[2], int maxcputime, Py_ssize_t maxmemory) { static const char *const kwlist[] = { "stdin", "stdout", "stderr", NULL }; static PyObject *dummy_args = NULL; Py_ssize_t i, argc; char **argv; bool own_args = false; int fdin = 0, fdout = 1, fderr = 2; if (dummy_args == NULL) { if (!(dummy_args = PyTuple_New(0))) { return -1; } } if (!PyArg_ParseTuple(args, "O:call", &args)) { return -1; } if (!PyArg_ParseTupleAndKeywords(dummy_args, kwds, "|O&O&O&:call", (char **) kwlist, fdconv, &fdin, fdconv, &fdout, fdconv, &fderr)) { return -1; } #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(args)) #else if (PyString_Check(args) || PyUnicode_Check(args)) #endif { argc = 1; args = PyTuple_Pack(1, args); if (args == NULL) { return -1; } own_args = true; } else if (!PySequence_Check(args)) { PyErr_SetString(PyExc_TypeError, "call() argument must be a sequence or string"); return -1; } else { argc = PySequence_Size(args); if (argc < 1) { PyErr_SetString(PyExc_TypeError, "call() argument must not be empty"); return -1; } } argv = PyMem_NEW(char *, argc + 1); if (argv == NULL) { if (own_args) { Py_DECREF(args); } PyErr_NoMemory(); return -1; } for (i = 0; i < argc; ++i) { if (!PyArg_Parse(PySequence_ITEM(args, i), "et", Py_FileSystemDefaultEncoding, &argv[i])) { free_string_array(argv, i); if (own_args) { Py_DECREF(args); } PyErr_SetString(PyExc_TypeError, "call() argument must contain only strings"); return -1; } } argv[argc] = NULL; curpid = fork(); if (!curpid) { pid_t pid; int spawn_errno, status, fd, fddupped[3]; struct child_stats stats; _PyTime_timeval tvstart, tvend; #if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3 struct rusage rusage; #endif #if defined RLIMIT_AS || defined RLIMIT_CPU struct rlimit rlimit; #endif /* Assume no errors occur: * POSIX:2008 doesn't even define any errors for setpgrp, nor does the (probably copied-verbatim-from-FreeBSD) man page on Mac OS X 10.6; * none of the error conditions POSIX:2008 does define for setpgid can occur. */ #ifdef HAVE_SETPGID setpgid(0, 0); #else //if defined HAVE_SETPGRP #ifdef SETPGRP_HAVE_ARG setpgrp(0, 0); #else setpgrp(); #endif #endif #ifdef SIGINT signal(SIGINT, SIG_DFL); #endif #if PY_MAJOR_VERSION > 3 || PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2 _Py_RestoreSignals(); #else #ifdef SIGPIPE signal(SIGPIPE, SIG_DFL); #endif #ifdef SIGXFSZ signal(SIGXFSZ, SIG_DFL); #endif #ifdef SIGXFZ signal(SIGXFZ, SIG_DFL); #endif #endif if (c2ppipe[1] < 3) { int newfd; #ifdef F_DUPFD_CLOEXEC newfd = fcntl(c2ppipe[1], F_DUPFD_CLOEXEC, 3); if (newfd == -1) { spawn_errno = errno; TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED); write(c2ppipe[1], &spawn_errno, sizeof spawn_errno); _exit(127); } c2ppipe[1] = newfd; #else newfd = fcntl(c2ppipe[1], F_DUPFD, 3); // Other threads should not fork/spawn right now if (newfd == -1 || fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) | FD_CLOEXEC) == -1) { spawn_errno = errno; TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED); write(c2ppipe[1], &spawn_errno, sizeof spawn_errno); _exit(127); } c2ppipe[1] = newfd; #endif } // Yes, this works as intended even if fdin == fdout == fderr == 0 // and there are no open file descriptors except 0 and c2ppipe // FIXME: error handling fddupped[0] = dup(fdin); fddupped[1] = dup(fdout); fddupped[2] = dup(fderr); dup2(fddupped[0], 0); dup2(fddupped[1], 1); dup2(fddupped[2], 2); // FIXME: close() may fail with EINTR or EIO; is setting CLOEXEC safer? // Bear in mind we still want to close them in _this_ process for (fd = sysconf(_SC_OPEN_MAX); --fd > c2ppipe[1]; ) { close(fd); } while (--fd >= 3) { close(fd); } #ifdef RLIMIT_AS if (maxmemory) { rlimit.rlim_cur = rlimit.rlim_max = maxmemory; setrlimit(RLIMIT_AS, &rlimit); } #endif #ifdef RLIMIT_CPU if (maxcputime) { rlimit.rlim_cur = rlimit.rlim_max = maxcputime; setrlimit(RLIMIT_CPU, &rlimit); } #endif #ifdef HAVE_SPAWN_H #ifdef __APPLE__ if (posix_spawnp != NULL) { #endif spawn_errno = posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ); _PyTime_gettimeofday(&tvstart); if (spawn_errno) { TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED); write(c2ppipe[1], &spawn_errno, sizeof spawn_errno); _exit(127); } #ifdef __APPLE__ } else #endif #endif #if !(defined HAVE_SPAWN_H) || defined __APPLE__ { pid = fork(); if (!pid) { execvp(argv[0], argv); spawn_errno = errno; TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED); write(c2ppipe[1], &spawn_errno, sizeof spawn_errno); _exit(127); } else if (pid == -1) { spawn_errno = errno; TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED); write(c2ppipe[1], &spawn_errno, sizeof spawn_errno); _exit(127); } else { _PyTime_gettimeofday(&tvstart); } } #endif TESTEE_REPORT_STATUS(TESTEE_SPAWNED); write(c2ppipe[1], &tvstart, sizeof tvstart); #ifdef HAVE_WAIT4 while (wait4(pid, &status, 0, &rusage) != pid); #elif defined HAVE_WAIT3 while (wait3(&status, 0, &rusage) != pid); #elif defined HAVE_WAITPID while (waitpid(pid, &status, 0) != pid); #else while (wait(&status) != pid); #endif _PyTime_gettimeofday(&tvend); #if defined HAVE_SYS_RESOURCE_H && !(defined HAVE_WAIT4 || defined HAVE_WAIT3) getrusage(RUSAGE_CHILDREN, &rusage); #endif stats = zero_stats; if (WIFEXITED(status) && WEXITSTATUS(status) == 127) _exit(127); else if (WIFSIGNALED(status)) stats.returncode = -WTERMSIG(status); else stats.returncode = WEXITSTATUS(status); timersub(&tvend, &tvstart, &stats.walltime); #if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3 timeradd(&rusage.ru_utime, &rusage.ru_stime, &stats.cputime); #ifdef __APPLE__ stats.memory = rusage.ru_maxrss; #else stats.memory = rusage.ru_maxrss << 10; #endif #endif write(c2ppipe[1], &stats, sizeof stats); _exit(0); } else if (curpid == -1) { PyErr_SetFromErrno(PyExc_OSError); free_string_array(argv, argc); if (own_args) { Py_DECREF(args); } return 0; } /* Assume no errors occur if the child is still alive: * the (probably copied-verbatim-from-FreeBSD) man page on Mac OS X 10.6 doesn't even define any errors for setpgrp; * none of the error conditions POSIX:2008 defines for setpgid can occur. */ #ifdef HAVE_SETPGID setpgid(curpid, 0); #elif defined SETPGRP_HAVE_ARG setpgrp(curpid, 0); #endif free_string_array(argv, argc); if (own_args) { Py_DECREF(args); } return 1; } static inline bool attr_to_timeval(PyObject *obj, const char *attr, _PyTime_timeval *ptv) { #ifdef HAVE_LONG_LONG long long i_whole; #else long i_whole; #endif PyObject *whole, *frac, *million, *usec, *usec_whole; PyObject *member = PyObject_GetAttrString(obj, attr); if (member == NULL) { return false; } if (member == Py_None) { Py_DECREF(member); timerclear(ptv); return true; } whole = PyNumber_Int(member); if (whole == NULL) { Py_DECREF(member); return false; } #ifdef HAVE_LONG_LONG i_whole = PyLong_AsLongLong(whole); #else i_whole = PyInt_AsLong(whole); #endif if (i_whole == -1 && PyErr_Occurred() != NULL) { Py_DECREF(whole); Py_DECREF(member); return false; } // FIXME: detect time_t overflow ptv->tv_sec = i_whole; frac = PyNumber_Subtract(member, whole); Py_DECREF(whole); Py_DECREF(member); if (frac == NULL) { return false; } million = PyInt_FromLong(1000000); if (million == NULL) { Py_DECREF(frac); return false; } usec = PyNumber_InPlaceMultiply(frac, million); Py_DECREF(million); Py_DECREF(frac); if (usec == NULL) { return false; } usec_whole = PyNumber_Int(usec); Py_DECREF(usec); if (usec_whole == NULL) { return false; } // FIXME: a sanity check (0 <= value < 1000000) here wouldn't harm ptv->tv_usec = PyInt_AsLong(usec_whole); Py_DECREF(usec_whole); return ptv->tv_usec != -1 || PyErr_Occurred() == NULL; } #ifdef __cplusplus typedef struct { char a[2]; } two_chars; static char is_int(char); static char is_int(signed char); static char is_int(unsigned char); static char is_int(short); static char is_int(unsigned short); static char is_int(int); static char is_int(unsigned); static char is_int(long); static char is_int(unsigned long); #ifdef HAVE_LONG_LONG static char is_int(long long); static char is_int(unsigned long long); #endif static two_chars is_int(...); #endif static inline bool timeval_to_attr(_PyTime_timeval *ptv, PyObject *obj, const char *attr) { PyObject *value; #ifdef __cplusplus // If tv_sec has an integral type and !tv_usec, try to create a Python int if (sizeof is_int(ptv->tv_sec) == sizeof(char) && !ptv->tv_usec) { if (ptv->tv_sec <= LONG_MAX) { value = PyInt_FromLong(ptv->tv_sec); } // FIXME: signed/unsigned comparisons ruin everything #ifdef HAVE_LONG_LONG else// if (ptv->tv_sec <= ULLONG_MAX) { value = PyLong_FromUnsignedLongLong(ptv->tv_sec); } #else // else if (ptv->tv_sec <= ULONG_MAX) // { // value = PyLong_FromUnsignedLong(ptv->tv_sec); // } //#endif else { value = PyFloat_FromDouble(ptv->tv_sec); } // #endif // } else #endif { // TODO: use decimal.Decimal or fractions.Fraction value = PyFloat_FromDouble(ptv->tv_sec + ptv->tv_usec * 0.000001); } if (value == NULL) { return false; } if (PyObject_SetAttrString(obj, attr, value) == -1) { return false; } Py_DECREF(value); return true; } /* TODO/FIXME: * Replace timeval->timespec and select->pselect if pselect is available (preferably only if pselect is not a wrapper around select). * File descriptors might be >= FD_SETSIZE? */ static PyObject *_unix_call(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *testcase = NULL, *obj; _unix__PopenPlaceholderObject *Popen_placeholder; int spawn_errno = 0, spawn_status, s, c2ppipe[2], retstat; struct child_stats stats = zero_stats; _PyTime_timeval maxwalltime, maxcputime, timeout, time_start; Py_ssize_t maxmemory, r; size_t stats_read = 0; fd_set readfds; char c; bool have_maxwalltime; if (kwds != NULL) { testcase = PyDict_GetItemString(kwds, "case"); } if (testcase == NULL) { PyErr_SetString(PyExc_TypeError, "call() requires a keyword argument 'case'"); return NULL; } Py_INCREF(testcase); PyDict_DelItemString(kwds, "case"); if (!attr_to_timeval(testcase, "maxwalltime", &maxwalltime) || !attr_to_timeval(testcase, "maxcputime", &maxcputime)) { Py_DECREF(testcase); return NULL; } obj = PyObject_GetAttrString(testcase, "maxmemory"); if (obj == NULL) { Py_DECREF(testcase); return NULL; } if (PyObject_IsTrue(obj)) { PyObject *factor, *bytes; factor = PyInt_FromLong(1024 * 1024); if (factor == NULL) { Py_DECREF(testcase); return NULL; } bytes = PyNumber_Multiply(obj, factor); Py_DECREF(factor); if (bytes == NULL) { Py_DECREF(testcase); return NULL; } maxmemory = PyNumber_AsSsize_t(bytes, PyExc_OverflowError); Py_DECREF(bytes); if (maxmemory == -1 && PyErr_Occurred() != NULL) { Py_DECREF(testcase); return NULL; } } else { maxmemory = 0; } Py_DECREF(obj); #ifdef HAVE_PIPE2 if (pipe2(c2ppipe, O_CLOEXEC)) { PyErr_SetFromErrno(PyExc_IOError); Py_DECREF(testcase); return NULL; } #else if (pipe(c2ppipe)) { PyErr_SetFromErrno(PyExc_IOError); Py_DECREF(testcase); return NULL; } // Does any other thread fork/spawn right now? Please shoot it in the head // (well, if this ends up causing trouble, anyway) if (fcntl(c2ppipe[0], F_SETFD, fcntl(c2ppipe[0], F_GETFD) | FD_CLOEXEC) == -1 || fcntl(c2ppipe[1], F_SETFD, fcntl(c2ppipe[1], F_GETFD) | FD_CLOEXEC) == -1) { PyErr_SetFromErrno(PyExc_IOError); close(c2ppipe[0]); close(c2ppipe[1]); Py_DECREF(testcase); return NULL; } #endif spawn_status = my_spawn(args, kwds, c2ppipe, maxcputime.tv_sec + (maxcputime.tv_usec > 0), maxmemory); close(c2ppipe[1]); if (!spawn_status) { PyObject *type, *value, *traceback, *e; close(c2ppipe[0]); Py_DECREF(testcase); PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); Py_XDECREF(traceback); Py_DECREF(type); e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL); Py_DECREF(value); PyErr_SetObject(CannotStartTestee, e); Py_DECREF(e); return NULL; } else if (spawn_status < 0) { close(c2ppipe[0]); Py_DECREF(testcase); return NULL; } // FIXME: use select in order not to miss SIGINT while ((r = read(c2ppipe[0], &c, 1)) == -1 && errno == EINTR) { if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } } if (r == 1) { if (c == TESTEE_SPAWNED) { size_t got = 0; while (got < sizeof time_start) { r = read(c2ppipe[0], got + (char *) &time_start, sizeof time_start - got); if (r > 0) { got += r; } else if (!r) { errno = 0; PyErr_SetFromErrno(PyExc_IOError); goto spawn_failed; } else if (errno == EINTR) { if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } } else { PyErr_SetFromErrno(PyExc_IOError); goto spawn_failed; } } if (!timeval_to_attr(&time_start, testcase, "time_started")) { close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } } else // if (c == TESTEE_SPAWN_FAILED) { size_t got = 0; while (got < sizeof spawn_errno) { r = read(c2ppipe[0], got + (char *) &spawn_errno, sizeof spawn_errno - got); if (r > 0) { got += r; } else if (!r) { // Can't get the real error; use zero instead spawn_errno = 0; break; } else if (errno == EINTR) { if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } } else { PyErr_SetFromErrno(PyExc_IOError); goto spawn_failed; } } errno = spawn_errno; /* if (errno == EACCES || errno == EINVAL || errno == ELOOP || errno == ENAMETOOLONG || errno == ENOENT || errno == ENOTDIR || errno == ENOEXEC || errno == ETXTBSY) { PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, PySequence_ITEM(args, 0)); } else {*/ PyErr_SetFromErrno(PyExc_OSError); //} goto spawn_failed; } } else { PyObject *type, *value, *traceback, *e; if (!r) errno = 0; PyErr_SetFromErrno(PyExc_IOError); spawn_failed: Py_DECREF(testcase); close(c2ppipe[0]); TERM_TESTEE; PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); Py_XDECREF(traceback); Py_DECREF(type); e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL); Py_DECREF(value); PyErr_SetObject(CannotStartTestee, e); Py_DECREF(e); return NULL; } Py_BEGIN_ALLOW_THREADS timeradd(&time_start, &maxwalltime, &time_end); FD_ZERO(&readfds); have_maxwalltime = timerisset(&maxwalltime); /* Implementations may place limitations on the maximum timeout interval supported. All implementations shall support a maximum timeout interval of at least 31 days. If the timeout argument specifies a timeout interval greater than the implementation- defined maximum value, the maximum value shall be used as the actual timeout value. (POSIX:2008) Therefore the loop and the && timercmp(&time_end, &now, <). */ for (;;) { _PyTime_timeval now; int maxfd = c2ppipe[0]; #ifdef HAVE_TERMIOS_H if (catch_escape) FD_SET(0, &readfds); #endif #ifdef USE_WAKEUP_FD FD_SET(intpipe[0], &readfds); if (intpipe[0] > maxfd) maxfd = intpipe[0]; #endif FD_SET(c2ppipe[0], &readfds); if (have_maxwalltime) { _PyTime_gettimeofday(&now); if (timercmp(&time_end, &now, <)) { timerclear(&timeout); } else { timersub(&time_end, &now, &timeout); } s = select(maxfd + 1, &readfds, NULL, NULL, &timeout); if (!s && timercmp(&time_end, &now, <)) { close(c2ppipe[0]); TERM_TESTEE; Py_BLOCK_THREADS Py_DECREF(testcase); PyErr_SetObject(WallTimeLimitExceeded, NULL); return NULL; } } else { s = select(maxfd + 1, &readfds, NULL, NULL, NULL); } if (s < 0 && errno == EINTR) { Py_BLOCK_THREADS if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } Py_UNBLOCK_THREADS } else if (s < 0 && errno != EAGAIN) { Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_IOError); close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } #ifdef USE_WAKEUP_FD else if (s > 0 && FD_ISSET(intpipe[0], &readfds)) { // FIXME: is error handling needed? while (read(intpipe[0], dont_care_buffer, sizeof dont_care_buffer) > 0); Py_BLOCK_THREADS if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } Py_UNBLOCK_THREADS } #endif #ifdef HAVE_TERMIOS_H else if (s > 0 && !FD_ISSET(c2ppipe[0], &readfds)) { // FIXME: is error and EOF handling needed? if ((r = read(0, &c, 1)) == 1) { if (c == '\33') { close(c2ppipe[0]); TERM_TESTEE; Py_BLOCK_THREADS Py_DECREF(testcase); PyErr_SetObject(CanceledByUser, NULL); return NULL; } } else if (r == -1 && errno == EINTR) { if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } } } #endif else if (s > 0) { bool blocked_threads = false; while ((r = read(c2ppipe[0], stats_read + (char *) &stats, sizeof stats - stats_read)) == -1 && errno == EINTR) { Py_BLOCK_THREADS blocked_threads = true; if (PyErr_CheckSignals() == -1) { PROPAGATE_SIGINT; close(c2ppipe[0]); Py_DECREF(testcase); TERM_TESTEE; return NULL; } } if (r > 0) { stats_read += r; } else if (!r) { break; } else { close(c2ppipe[0]); TERM_TESTEE; if (!blocked_threads) { Py_BLOCK_THREADS } Py_DECREF(testcase); PyErr_SetFromErrno(PyExc_IOError); return NULL; } if (blocked_threads) { Py_UNBLOCK_THREADS } } } close(c2ppipe[0]); Py_END_ALLOW_THREADS #ifdef HAVE_WAITPID while (waitpid(curpid, &retstat, 0) != curpid) #else while (wait(&retstat) != curpid) #endif { if (PyErr_CheckSignals() == -1) { Py_DECREF(testcase); return NULL; } } if (WIFEXITED(retstat) && WEXITSTATUS(retstat) == 127) { PyObject *type, *value, *traceback, *e; Py_DECREF(testcase); errno = 0; PyErr_SetFromErrno(PyExc_OSError); PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); Py_XDECREF(traceback); Py_DECREF(type); e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL); Py_DECREF(value); PyErr_SetObject(CannotStartTestee, e); Py_DECREF(e); return NULL; } else if (!WIFEXITED(retstat) || WEXITSTATUS(retstat)) { Py_DECREF(testcase); if (WIFSTOPPED(retstat)) { return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: stopped by signal %d", WSTOPSIG(retstat)); } else if (WIFSIGNALED(retstat)) { return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: terminated by signal %d", WTERMSIG(retstat)); } else if (WIFEXITED(retstat)) { return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: %d", WEXITSTATUS(retstat)); } else { PyErr_SetString(PyExc_EnvironmentError, "unexpected exit status from worker: not exited, signaled or stopped"); return NULL; } } if (stats_read != sizeof stats) { Py_DECREF(testcase); PyErr_SetString(PyExc_EnvironmentError, "unexpectedly early end of output from worker"); return NULL; } if (timerisset(&maxwalltime) && timercmp(&stats.walltime, &maxwalltime, >)) { Py_DECREF(testcase); PyErr_SetObject(WallTimeLimitExceeded, NULL); return NULL; } obj = PyInt_FromLong(0); if (obj == NULL) { Py_DECREF(testcase); return NULL; } if (PyObject_SetAttrString(testcase, "time_started", obj) == -1) { Py_DECREF(testcase); return NULL; } Py_DECREF(obj); #if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3 if (timerisset(&maxcputime) || !timerisset(&maxwalltime)) { PyObject *cputls; if (!timeval_to_attr(&stats.cputime, testcase, "time_stopped")) { Py_DECREF(testcase); return NULL; } cputls = PyObject_GetAttrString(testcase, "cpu_time_limit_string"); if (cputls == NULL) { Py_DECREF(testcase); return NULL; } if (PyObject_SetAttrString(testcase, "time_limit_string", cputls) == -1) { Py_DECREF(testcase); return NULL; } Py_DECREF(cputls); if (timerisset(&maxcputime) && timercmp(&stats.cputime, &maxcputime, >)) { Py_DECREF(testcase); PyErr_SetObject(CPUTimeLimitExceeded, NULL); return NULL; } } else #endif { if (!timeval_to_attr(&stats.walltime, testcase, "time_stopped")) { Py_DECREF(testcase); return NULL; } } #if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3 if (maxmemory && stats.memory > maxmemory) { Py_DECREF(testcase); PyErr_SetObject(MemoryLimitExceeded, NULL); return NULL; } #endif Popen_placeholder = PyObject_New(_unix__PopenPlaceholderObject, &_unix__PopenPlaceholderType); if (Popen_placeholder == NULL) { return NULL; } Popen_placeholder->returncode = stats.returncode; PyObject_SetAttrString(testcase, "process", (PyObject *) Popen_placeholder); Py_DECREF(Popen_placeholder); Py_DECREF(testcase); Py_RETURN_NONE; } static PyObject *_unix_pause(PyObject *self) { #ifdef HAVE_TERMIOS_H if (catch_escape) { char c; while (read(0, &c, 1) == -1 && errno == EINTR); } #endif Py_RETURN_NONE; } static PyMethodDef _unixMethods[] = { { "call", (PyCFunction) _unix_call, METH_VARARGS | METH_KEYWORDS, "Call a process." }, { "pause", (PyCFunction) _unix_pause, METH_NOARGS, "Block until a key is pressed." }, { NULL } }; #ifdef USE_WAKEUP_FD static void close_intpipe(void) { close(intpipe[0]); close(intpipe[1]); intpipe[0] = intpipe[1] = 0; PySignal_SetWakeupFd(-1); } #endif #ifdef HAVE_TERMIOS_H static void restore_termios(void) { tcsetattr(0, TCSAFLUSH, &orig_termios); #ifdef USE_WAKEUP_FD close_intpipe(); #endif } #endif #if PY_MAJOR_VERSION >= 3 #define INIT_FAIL return NULL static PyModuleDef _unixmodule = { PyModuleDef_HEAD_INIT, "_unix", NULL, -1, _unixMethods }; PyMODINIT_FUNC PyInit__unix(void) #else #define INIT_FAIL return PyMODINIT_FUNC init_unix(void) #endif { #ifdef HAVE_TERMIOS_H struct termios new_termios; #endif PyObject *exceptions, *module; _unix__PopenPlaceholderType.tp_new = PyType_GenericNew; if (PyType_Ready(&_unix__PopenPlaceholderType) == -1) { INIT_FAIL; } exceptions = PyImport_ImportModule("upreckon.exceptions"); if (exceptions == NULL || (CannotStartTestee = PyObject_GetAttrString(exceptions, "CannotStartTestee")) == NULL || (CanceledByUser = PyObject_GetAttrString(exceptions, "CanceledByUser")) == NULL || (WallTimeLimitExceeded = PyObject_GetAttrString(exceptions, "WallTimeLimitExceeded")) == NULL || (CPUTimeLimitExceeded = PyObject_GetAttrString(exceptions, "CPUTimeLimitExceeded")) == NULL || (MemoryLimitExceeded = PyObject_GetAttrString(exceptions, "MemoryLimitExceeded")) == NULL) { Py_XDECREF(MemoryLimitExceeded); Py_XDECREF(CPUTimeLimitExceeded); Py_XDECREF(WallTimeLimitExceeded); Py_XDECREF(CanceledByUser); Py_XDECREF(CannotStartTestee); Py_XDECREF(exceptions); INIT_FAIL; } Py_DECREF(exceptions); #ifdef WITH_NEXT_FRAMEWORK if (environ == NULL) { environ = *_NSGetEnviron(); } #endif #ifdef USE_WAKEUP_FD if (!intpipe[0] || !intpipe[1]) { #ifdef HAVE_PIPE2 if (pipe2(intpipe, O_CLOEXEC | O_NONBLOCK)) { PyErr_SetFromErrno(PyExc_IOError); Py_DECREF(MemoryLimitExceeded); Py_DECREF(CPUTimeLimitExceeded); Py_DECREF(WallTimeLimitExceeded); Py_DECREF(CanceledByUser); Py_DECREF(CannotStartTestee); INIT_FAIL; } #else if (pipe(intpipe)) { PyErr_SetFromErrno(PyExc_IOError); Py_DECREF(MemoryLimitExceeded); Py_DECREF(CPUTimeLimitExceeded); Py_DECREF(WallTimeLimitExceeded); Py_DECREF(CanceledByUser); Py_DECREF(CannotStartTestee); INIT_FAIL; } // Other threads must not fork now if (fcntl(intpipe[0], F_SETFD, fcntl(intpipe[0], F_GETFD) | FD_CLOEXEC) == -1 || fcntl(intpipe[1], F_SETFD, fcntl(intpipe[1], F_GETFD) | FD_CLOEXEC) == -1 || fcntl(intpipe[0], F_SETFL, fcntl(intpipe[0], F_GETFL) | O_NONBLOCK) == -1 || fcntl(intpipe[1], F_SETFL, fcntl(intpipe[1], F_GETFL) | O_NONBLOCK) == -1) { PyErr_SetFromErrno(PyExc_IOError); close(intpipe[0]); close(intpipe[1]); Py_DECREF(MemoryLimitExceeded); Py_DECREF(CPUTimeLimitExceeded); Py_DECREF(WallTimeLimitExceeded); Py_DECREF(CanceledByUser); Py_DECREF(CannotStartTestee); INIT_FAIL; } #endif } #endif #if PY_MAJOR_VERSION >= 3 module = PyModule_Create(&_unixmodule); #else module = Py_InitModule("_unix", _unixMethods); #endif if (module == NULL) { #ifdef USE_WAKEUP_FD close(intpipe[0]); close(intpipe[1]); #endif Py_DECREF(MemoryLimitExceeded); Py_DECREF(CPUTimeLimitExceeded); Py_DECREF(WallTimeLimitExceeded); Py_DECREF(CanceledByUser); Py_DECREF(CannotStartTestee); INIT_FAIL; } #ifdef HAVE_TERMIOS_H if (!tcgetattr(0, &orig_termios)) { new_termios = orig_termios; // Stolen from tty.py of Python 2.7.1 new_termios.c_lflag &= ~(ECHO | ICANON); new_termios.c_cc[VMIN] = 1; new_termios.c_cc[VTIME] = 0; if (!Py_AtExit(restore_termios) && !tcsetattr(0, TCSAFLUSH, &new_termios)) { catch_escape = true; } } #ifdef USE_WAKEUP_FD else { Py_AtExit(close_intpipe); } #endif #elif defined USE_WAKEUP_FD Py_AtExit(close_intpipe); #endif #ifdef USE_WAKEUP_FD PySignal_SetWakeupFd(intpipe[1]); #endif #if PY_MAJOR_VERSION >= 3 return module; #endif }