diff -u python2.5-2.5/debian/rules python2.5-2.5/debian/rules --- python2.5-2.5/debian/rules +++ python2.5-2.5/debian/rules @@ -915,6 +915,7 @@ egg-info-no-version \ debug-build \ hotshot-import \ + subprocess-eintr-safety \ # pydebug-path \ diff -u python2.5-2.5/debian/changelog python2.5-2.5/debian/changelog --- python2.5-2.5/debian/changelog +++ python2.5-2.5/debian/changelog @@ -1,3 +1,19 @@ +python2.5 (2.5-5ubuntu10) feisty; urgency=low + + * Add debian/patches/subprocess-eintr-safety.dpatch: + - Create and use wrappers around read(), write(), and os.waitpid() in the + subprocess module which retry the operation on an EINTR (which happens + if e. g. an alarm was raised while the system call was in progress). It is + incredibly hard and inconvenient to sensibly handle this in + applications, so let's fix this at the right level. + - Patch based on original proposal of Peter Åstrand in + http://sourceforge.net/tracker/index.php?func=detail&aid=1068268&group_id=5470&atid=105470 + - Add two test cases. + - (LP: #87292) + * debian/rules: Apply above patch. + + -- Martin Pitt Thu, 15 Mar 2007 09:20:37 +0100 + python2.5 (2.5-5ubuntu9) feisty; urgency=medium * Update to 20070307, taken from the 2.5 release branch. only in patch2: unchanged: --- python2.5-2.5.orig/debian/patches/subprocess-eintr-safety.dpatch +++ python2.5-2.5/debian/patches/subprocess-eintr-safety.dpatch @@ -0,0 +1,221 @@ +#! /bin/sh -e + +dir= +if [ $# -eq 3 -a "$2" = '-d' ]; then + pdir="-d $3" + dir="$3/" +elif [ $# -ne 1 ]; then + echo >&2 "usage: `basename $0`: -patch|-unpatch [-d ]" + exit 1 +fi +case "$1" in + -patch) + patch $pdir -f --no-backup-if-mismatch -p0 < $0 + #cd ${dir}gcc && autoconf + ;; + -unpatch) + patch $pdir -f --no-backup-if-mismatch -R -p0 < $0 + #rm ${dir}gcc/configure + ;; + *) + echo >&2 "usage: `basename $0`: -patch|-unpatch [-d ]" + exit 1 +esac +exit 0 + +--- Lib/subprocess.py 2007-03-14 19:16:36.000000000 +0100 ++++ Lib/subprocess.py 2007-03-14 19:18:50.000000000 +0100 +@@ -655,12 +655,12 @@ class Popen(object): + stderr = None + if self.stdin: + if input: +- self.stdin.write(input) ++ self._fo_write_no_intr(self.stdin, input) + self.stdin.close() + elif self.stdout: +- stdout = self.stdout.read() ++ stdout = self._fo_read_no_intr(self.stdout) + elif self.stderr: +- stderr = self.stderr.read() ++ stderr = self._fo_read_no_intr(self.stderr) + self.wait() + return (stdout, stderr) + +@@ -977,6 +977,62 @@ class Popen(object): + pass + + ++ def _read_no_intr(self, fd, buffersize): ++ """Like os.read, but retries on EINTR""" ++ while True: ++ try: ++ return os.read(fd, buffersize) ++ except OSError, e: ++ if e.errno == errno.EINTR: ++ continue ++ else: ++ raise ++ ++ ++ def _write_no_intr(self, fd, s): ++ """Like os.write, but retries on EINTR""" ++ while True: ++ try: ++ return os.write(fd, s) ++ except OSError, e: ++ if e.errno == errno.EINTR: ++ continue ++ else: ++ raise ++ ++ def _waitpid_no_intr(self, pid, options): ++ """Like os.waitpid, but retries on EINTR""" ++ while True: ++ try: ++ return os.waitpid(pid, options) ++ except OSError, e: ++ if e.errno == errno.EINTR: ++ continue ++ else: ++ raise ++ ++ def _fo_read_no_intr(self, obj): ++ """Like obj.read(), but retries on EINTR""" ++ while True: ++ try: ++ return obj.read() ++ except IOError, e: ++ if e.errno == errno.EINTR: ++ continue ++ else: ++ raise ++ ++ def _fo_write_no_intr(self, obj, data): ++ """Like obj.write(), but retries on EINTR""" ++ while True: ++ try: ++ return obj.write(data) ++ except IOError, e: ++ if e.errno == errno.EINTR: ++ continue ++ else: ++ raise ++ + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, +@@ -1055,7 +1121,7 @@ class Popen(object): + exc_value, + tb) + exc_value.child_traceback = ''.join(exc_lines) +- os.write(errpipe_write, pickle.dumps(exc_value)) ++ self._write_no_intr(errpipe_write, pickle.dumps(exc_value)) + + # This exitcode won't be reported to applications, so it + # really doesn't matter what we return. +@@ -1071,10 +1137,10 @@ class Popen(object): + os.close(errwrite) + + # Wait for exec to fail or succeed; possibly raising exception +- data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB ++ data = self._read_no_intr(errpipe_read, 1048576) # Exceptions limited to 1 MB + os.close(errpipe_read) + if data != "": +- os.waitpid(self.pid, 0) ++ self._waitpid_no_intr(self.pid, 0) + child_exception = pickle.loads(data) + raise child_exception + +@@ -1094,7 +1160,7 @@ class Popen(object): + attribute.""" + if self.returncode is None: + try: +- pid, sts = os.waitpid(self.pid, os.WNOHANG) ++ pid, sts = self._waitpid_no_intr(self.pid, os.WNOHANG) + if pid == self.pid: + self._handle_exitstatus(sts) + except os.error: +@@ -1107,7 +1173,7 @@ class Popen(object): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: +- pid, sts = os.waitpid(self.pid, 0) ++ pid, sts = self._waitpid_no_intr(self.pid, 0) + self._handle_exitstatus(sts) + return self.returncode + +@@ -1135,27 +1201,33 @@ class Popen(object): + + input_offset = 0 + while read_set or write_set: +- rlist, wlist, xlist = select.select(read_set, write_set, []) ++ try: ++ rlist, wlist, xlist = select.select(read_set, write_set, []) ++ except select.error, e: ++ if e[0] == errno.EINTR: ++ continue ++ else: ++ raise + + if self.stdin in wlist: + # When select has indicated that the file is writable, + # we can write up to PIPE_BUF bytes without risk + # blocking. POSIX defines PIPE_BUF >= 512 +- bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512)) ++ bytes_written = self._write_no_intr(self.stdin.fileno(), buffer(input, input_offset, 512)) + input_offset += bytes_written + if input_offset >= len(input): + self.stdin.close() + write_set.remove(self.stdin) + + if self.stdout in rlist: +- data = os.read(self.stdout.fileno(), 1024) ++ data = self._read_no_intr(self.stdout.fileno(), 1024) + if data == "": + self.stdout.close() + read_set.remove(self.stdout) + stdout.append(data) + + if self.stderr in rlist: +- data = os.read(self.stderr.fileno(), 1024) ++ data = self._read_no_intr(self.stderr.fileno(), 1024) + if data == "": + self.stderr.close() + read_set.remove(self.stderr) +--- Lib/test/test_subprocess.py 2007-03-14 19:16:36.000000000 +0100 ++++ Lib/test/test_subprocess.py 2007-03-14 19:18:57.000000000 +0100 +@@ -580,6 +578,34 @@ class ProcessTestCase(unittest.TestCase) + os.remove(fname) + self.assertEqual(rc, 47) + ++ def test_eintr(self): ++ # retries on EINTR for an argv ++ ++ # send ourselves a signal that causes EINTR ++ prev_handler = signal.signal(signal.SIGALRM, lambda x,y: 1) ++ signal.alarm(1) ++ time.sleep(0.5) ++ ++ rc = subprocess.Popen(['sleep', '1']) ++ self.assertEqual(rc.wait(), 0) ++ ++ signal.signal(signal.SIGALRM, prev_handler) ++ ++ def test_eintr_out(self): ++ # retries on EINTR for a shell call and pipelining ++ ++ # send ourselves a signal that causes EINTR ++ prev_handler = signal.signal(signal.SIGALRM, lambda x,y: 1) ++ signal.alarm(1) ++ time.sleep(0.5) ++ ++ rc = subprocess.Popen("sleep 1; echo hello", ++ shell=True, stdout=subprocess.PIPE) ++ out = rc.communicate()[0] ++ self.assertEqual(rc.returncode, 0) ++ self.assertEqual(out, "hello\n") ++ ++ signal.signal(signal.SIGALRM, prev_handler) + + # + # Windows tests