Below I attached a script that reproduces the behavior of an assertion error on a specific fork condition for a process with a pty. Here are the results
Xubuntu x64:
Python 2.7, parent_close_slave_before_child_write = False: WORKS
Python 2.7, parent_close_slave_before_child_write = True: WORKS
Python 3.2, parent_close_slave_before_child_write = False: WORKS
Python 3.2, parent_close_slave_before_child_write = True: WORKS
Mac OSX 10.7.2:
Python 2.7, parent_close_slave_before_child_write = False: WORKS
Python 2.7, parent_close_slave_before_child_write = True: WORKS
Python 3.2, parent_close_slave_before_child_write = False: >> FAILS <<
Python 3.2, parent_close_slave_before_child_write = True: WORKS
Here's dtruss -f for python 2.7 and 3.0 on Mac OSX:
2.7, parent_close_slave_before_child_write = False (WORKS)
PID/THRD SYSCALL(args) = return
356/0xc4a: open_nocancel("/dev/ptmx\0", 0x20002, 0x0) = 3 0
356/0xc4a: ioctl(0x3, 0x20007454, 0xFFFFFFFF) = 0 0
356/0xc4a: ioctl(0x3, 0x20007452, 0x0) = 0 0
356/0xc4a: ioctl(0x3, 0x40807453, 0x10041AD20) = 0 0
356/0xc4a: stat64("/dev/ttys003\0", 0x7FFF5FBFF030, 0x0) = 0 0
356/0xc4a: open_nocancel("/dev/ttys003\0", 0x20002, 0x0) = 4 0
356/0xc4a: fork() = 362 0
362/0xc61: fork() = 0 0
362/0xc61: thread_selfid(0x100420BE0, 0x3, 0x1) = 3169 0
362/0xc61: getpid(0x100420BE0, 0x3, 0x0) = 362 0
362/0xc61: select(0x0, 0x0, 0x0, 0x0, 0x7FFF5FBFF3C0) = 0 0
362/0xc61: setsid(0x0, 0x0, 0x1001A7B40) = 362 0
362/0xc61: close(0x3) = 0 0
362/0xc61: dup2(0x4, 0x0, 0x0) = 0 0
362/0xc61: dup2(0x4, 0x1, 0x0) = 1 0
362/0xc61: dup2(0x4, 0x2, 0x0) = 2 0
362/0xc61: getrlimit(0x1008, 0x7FFF5FBFF3D0, 0x0) = 0 0
... close range ...
362/0xc61: ioctl(0x1, 0x40487413, 0x7FFF5FBFF360) = 0 0
362/0xc61: fstat64(0x1, 0x7FFF5FBFF2D0, 0x0) = 0 0
362/0xc61: open_nocancel("/dev/\0", 0x100004, 0x0) = 3 0
362/0xc61: fcntl_nocancel(0x3, 0x2, 0x1) = 0 0
362/0xc61: fstatfs64(0x3, 0x7FFF5FBFE7B0, 0x0) = 0 0
362/0xc61: getdirentries64(0x3, 0x1010AB600, 0x1000) = 3080 0
... lots of lstat64 ...
362/0xc61: close_nocancel(0x3) = 0 0
362/0xc61: open("/dev/ttys003\0", 0x2, 0x1FF) = 3 0
362/0xc61: close(0x3) = 0 0
362/0xc61: write(0x1, "testing\0", 0x7) = 7 0
362/0xc61: close(0x1) = 0 0
356/0xc4a: select(0x0, 0x0, 0x0, 0x0, 0x7FFF5FBFF3C0) = 0 0
356/0xc4a: close(0x4) = 0 0
356/0xc4a: read(0x3, "testing\0", 0x400) = 7 0
356/0xc4a: write_nocancel(0x1, "'testing'\n\0", 0xA) = 10 0
356/0xc4a: wait4(0x16A, 0x7FFF5FBFF3D4, 0x0) = 362 0
356/0xc4a: sigaction(0x2, 0x7FFF5FBFF800, 0x7FFF5FBFF830) = 0 0
3.0, parent_close_slave_before_child_write = False (FAILS)
PID/THRD SYSCALL(args) = return
385/0xe53: open_nocancel("/dev/ptmx\0", 0x20002, 0xD15EB8) = 3 0
385/0xe53: ioctl(0x3, 0x20007454, 0xBFFFF1BC) = 0 0
385/0xe53: ioctl(0x3, 0x20007452, 0xBFFFF1BC) = 0 0
385/0xe53: ioctl(0x3, 0x40807453, 0x64F220) = 0 0
385/0xe53: stat64("/dev/ttys003\0", 0xBFFFF164, 0x64F220) = 0 0
385/0xe53: open_nocancel("/dev/ttys003\0", 0x20002, 0x0) = 4 0
385/0xe53: fork() = 389 0
389/0xe5c: fork() = 0 0
389/0xe5c: thread_selfid(0x0, 0x0, 0x0) = 3676 0
389/0xe5c: getpid(0x0, 0x0, 0x0) = 389 0
389/0xe5c: select_nocancel(0x0, 0x0, 0x0) = 0 0
389/0xe5c: setsid(0x0, 0x0, 0x0) = 389 0
389/0xe5c: close_nocancel(0x3) = 0 0
389/0xe5c: dup2(0x4, 0x0, 0x0) = 0 0
389/0xe5c: dup2(0x4, 0x1, 0x0) = 1 0
389/0xe5c: dup2(0x4, 0x2, 0x0) = 2 0
389/0xe5c: getrlimit(0x8, 0xBFFFF46C, 0x0) = 0 0
... close range ...
389/0xe5c: ioctl(0x1, 0x402C7413, 0xBFFFF410) = 0 0
389/0xe5c: fstat64(0x1, 0xBFFFF3A4, 0xBFFFF410) = 0 0
389/0xe5c: open_nocancel("/dev/\0", 0x100004, 0xBFFFE968) = 3 0
389/0xe5c: fcntl_nocancel(0x3, 0x2, 0x1) = 0 0
389/0xe5c: fstatfs64(0x3, 0xBFFFE8D8, 0x1) = 0 0
389/0xe5c: getdirentries64(0x3, 0x10DFC00, 0x1000) = 3080 0
... lots of lstat64 ...
389/0xe5c: close_nocancel(0x3) = 0 0
389/0xe5c: open_nocancel("/dev/ttys003\0", 0x20002, 0x0) = 3 0
389/0xe5c: close_nocancel(0x3) = 0 0
389/0xe5c: write_nocancel(0x1, "testing\0", 0x7) = 7 0
389/0xe5c: close_nocancel(0x1) = 0 0
385/0xe53: select_nocancel(0x0, 0x0, 0x0) = 0 0
385/0xe53: close_nocancel(0x4) = 0 0
385/0xe53: read_nocancel(0x3, "\0", 0x400) = 0 0
385/0xe53: write_nocancel(0x1, "b''\n\0", 0x4) = 4 0
385/0xe53: wait4_nocancel(0x185, 0xBFFFF474, 0x0) = 389 0
385/0xe53: sigaction(0x2, 0xBFFFF800, 0xBFFFF838) = 0 0
385/0xe53: madvise(0xDA0000, 0x20000, 0x9) = 0 0
385/0xe53: madvise(0xDC0000, 0x20000, 0x9) = 0 0
385/0xe53: madvise(0xD80000, 0x20000, 0x9) = 0 0
2.7, parent_close_slave_before_child_write = True (WORkS)
PID/THRD SYSCALL(args) = return
363/0xcab: open_nocancel("/dev/ptmx\0", 0x20002, 0x0) = 3 0
363/0xcab: ioctl(0x3, 0x20007454, 0xFFFFFFFF) = 0 0
363/0xcab: ioctl(0x3, 0x20007452, 0x0) = 0 0
363/0xcab: ioctl(0x3, 0x40807453, 0x10041AD20) = 0 0
363/0xcab: stat64("/dev/ttys003\0", 0x7FFF5FBFF030, 0x0) = 0 0
363/0xcab: open_nocancel("/dev/ttys003\0", 0x20002, 0x0) = 4 0
363/0xcab: fork() = 368 0
368/0xcb5: fork() = 0 0
368/0xcb5: thread_selfid(0x100420BE0, 0x3, 0x1) = 3253 0
368/0xcb5: getpid(0x100420BE0, 0x3, 0x0) = 368 0
363/0xcab: select(0x0, 0x0, 0x0, 0x0, 0x7FFF5FBFF3C0) = 0 0
363/0xcab: close(0x4) = 0 0
368/0xcb5: select(0x0, 0x0, 0x0, 0x0, 0x7FFF5FBFF3C0) = 0 0
368/0xcb5: setsid(0x0, 0x0, 0x1001A7B40) = 368 0
368/0xcb5: close(0x3) = 0 0
368/0xcb5: dup2(0x4, 0x0, 0x0) = 0 0
368/0xcb5: dup2(0x4, 0x1, 0x0) = 1 0
368/0xcb5: dup2(0x4, 0x2, 0x0) = 2 0
368/0xcb5: getrlimit(0x1008, 0x7FFF5FBFF3D0, 0x0) = 0 0
... close range ...
368/0xcb5: ioctl(0x1, 0x40487413, 0x7FFF5FBFF360) = 0 0
368/0xcb5: fstat64(0x1, 0x7FFF5FBFF2D0, 0x0) = 0 0
368/0xcb5: open_nocancel("/dev/\0", 0x100004, 0x0) = 3 0
368/0xcb5: fcntl_nocancel(0x3, 0x2, 0x1) = 0 0
368/0xcb5: fstatfs64(0x3, 0x7FFF5FBFE7B0, 0x0) = 0 0
368/0xcb5: getdirentries64(0x3, 0x1010AB600, 0x1000) = 3080 0
... lots of lstat64 ...
368/0xcb5: close_nocancel(0x3) = 0 0
368/0xcb5: open("/dev/ttys003\0", 0x2, 0x1FF) = 3 0
368/0xcb5: close(0x3) = 0 0
368/0xcb5: write(0x1, "testing\0", 0x7) = 7 0
368/0xcb5: close(0x1) = 0 0
363/0xcab: read(0x3, "testing\0", 0x400) = 7 0
363/0xcab: write_nocancel(0x1, "'testing'\n\0", 0xA) = 10 0
363/0xcab: wait4(0x170, 0x7FFF5FBFF3D4, 0x0) = 368 0
363/0xcab: sigaction(0x2, 0x7FFF5FBFF800, 0x7FFF5FBFF830) = 0 0
3.0, parent_close_slave_before_child_write = True (WORkS)
PID/THRD SYSCALL(args) = return
393/0xed3: open_nocancel("/dev/ptmx\0", 0x20002, 0xD15EB8) = 3 0
393/0xed3: ioctl(0x3, 0x20007454, 0xBFFFF1BC) = 0 0
393/0xed3: ioctl(0x3, 0x20007452, 0xBFFFF1BC) = 0 0
393/0xed3: ioctl(0x3, 0x40807453, 0x64F220) = 0 0
393/0xed3: stat64("/dev/ttys003\0", 0xBFFFF164, 0x64F220) = 0 0
393/0xed3: open_nocancel("/dev/ttys003\0", 0x20002, 0x0) = 4 0
393/0xed3: fork() = 399 0
399/0xee3: fork() = 0 0
399/0xee3: thread_selfid(0x0, 0x0, 0x0) = 3811 0
399/0xee3: getpid(0x0, 0x0, 0x0) = 399 0
393/0xed3: select_nocancel(0x0, 0x0, 0x0) = 0 0
393/0xed3: close_nocancel(0x4) = 0 0
399/0xee3: select_nocancel(0x0, 0x0, 0x0) = 0 0
399/0xee3: setsid(0x0, 0x0, 0x0) = 399 0
399/0xee3: close_nocancel(0x3) = 0 0
399/0xee3: dup2(0x4, 0x0, 0x0) = 0 0
399/0xee3: dup2(0x4, 0x1, 0x0) = 1 0
399/0xee3: dup2(0x4, 0x2, 0x0) = 2 0
399/0xee3: getrlimit(0x8, 0xBFFFF46C, 0x0) = 0 0
... close range ...
399/0xee3: ioctl(0x1, 0x402C7413, 0xBFFFF410) = 0 0
399/0xee3: fstat64(0x1, 0xBFFFF3A4, 0xBFFFF410) = 0 0
399/0xee3: open_nocancel("/dev/\0", 0x100004, 0xBFFFE968) = 3 0
399/0xee3: fcntl_nocancel(0x3, 0x2, 0x1) = 0 0
399/0xee3: fstatfs64(0x3, 0xBFFFE8D8, 0x1) = 0 0
399/0xee3: getdirentries64(0x3, 0x10DFC00, 0x1000) = 3080 0
... lots of lstat64 ...
399/0xee3: close_nocancel(0x3) = 0 0
399/0xee3: open_nocancel("/dev/ttys003\0", 0x20002, 0x0) = 3 0
399/0xee3: close_nocancel(0x3) = 0 0
399/0xee3: write_nocancel(0x1, "testing\0", 0x7) = 7 0
399/0xee3: close_nocancel(0x1) = 0 0
393/0xed3: read_nocancel(0x3, "testing\0", 0x400) = 7 0
393/0xed3: write_nocancel(0x1, "b'testing'\n\0", 0xB) = 11 0
393/0xed3: wait4_nocancel(0x18F, 0xBFFFF474, 0x0) = 399 0
393/0xed3: sigaction(0x2, 0xBFFFF800, 0xBFFFF838) = 0 0
393/0xed3: madvise(0xDA0000, 0x20000, 0x9) = 0 0
393/0xed3: madvise(0xDC0000, 0x20000, 0x9) = 0 0
393/0xed3: madvise(0xD80000, 0x20000, 0x9) = 0 0
-----------
import os
import pty
import resource
import signal
import tty
import time
import sys
PY3 = sys.version_info.major == 3
if PY3: raw_input = input
# change this to False for failure on py3
parent_close_slave_before_child_write = True
print("pid: %d" % os.getpid())
raw_input("hit enter when 'dtruss -f' is ready> ")
send_from_child = "testing123".encode()
master, slave = pty.openpty()
pid = os.fork()
# child process
if pid == 0:
if parent_close_slave_before_child_write: time.sleep(2)
else: time.sleep(1)
os.setsid()
os.close(master)
os.dup2(slave, 0)
os.dup2(slave, 1)
os.dup2(slave, 2)
max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
os.closerange(3, max_fd)
# make controlling terminal. taken from pty.fork
tmp_fd = os.open(os.ttyname(1), os.O_RDWR)
os.close(tmp_fd)
os.write(1, send_from_child)
os.close(1)
os._exit(255)
# parent process
else:
if parent_close_slave_before_child_write: time.sleep(1)
else: time.sleep(2)
os.close(slave)
actual_output = os.read(master, 1024)
os.waitpid(pid, 0)
assert(actual_output == send_from_child)
print("correct output") |