# from shells and from binaries that belong to portage (the number of entry
# points is minimized). The "tee" binary is not among the allowed entry
# points, so it is spawned outside of the sesandbox domain and reads from a
- # pipe between two domains.
+ # pseudo-terminal that connects two domains.
logfile = keywords.get("logfile")
mypids = []
- pw = None
+ slave_fd = None
+ output_pid = None
+ input_pid = None
if logfile:
del keywords["logfile"]
fd_pipes = keywords.get("fd_pipes")
fd_pipes = {0:0, 1:1, 2:2}
elif 1 not in fd_pipes or 2 not in fd_pipes:
raise ValueError(fd_pipes)
- pr, pw = os.pipe()
- mypids.extend(portage.process.spawn(('tee', '-i', '-a', logfile),
- returnpid=True, fd_pipes={0:pr, 1:fd_pipes[1], 2:fd_pipes[2]}))
- os.close(pr)
- fd_pipes[1] = pw
- fd_pipes[2] = pw
+ from pty import openpty
+ master_fd, slave_fd = openpty()
+ # Disable the ECHO attribute so the terminal behaves properly
+ # if the subprocess needs to read input from stdin.
+ import termios
+ term_attr = termios.tcgetattr(master_fd)
+ term_attr[3] &= ~termios.ECHO
+ termios.tcsetattr(master_fd, termios.TCSAFLUSH, term_attr)
+ # tee will always exit with an IO error, so ignore it's stderr.
+ null_file = open('/dev/null', 'w')
+ mypids.extend(portage.process.spawn(['tee', '-i', '-a', logfile],
+ returnpid=True, fd_pipes={0:master_fd, 1:fd_pipes[1],
+ 2:null_file.fileno()}))
+ output_pid = mypids[-1]
+ mypids.extend(portage.process.spawn(['cat'],
+ returnpid=True, fd_pipes={0:fd_pipes[0], 1:master_fd,
+ 2:null_file.fileno()}))
+ input_pid = mypids[-1]
+ os.close(master_fd)
+ null_file.close()
+ fd_pipes[0] = slave_fd
+ fd_pipes[1] = slave_fd
+ fd_pipes[2] = slave_fd
keywords["fd_pipes"] = fd_pipes
features = mysettings.features
try:
mypids.extend(spawn_func(mystring, env=env, **keywords))
finally:
- if pw:
- os.close(pw)
+ if slave_fd:
+ os.close(slave_fd)
if sesandbox:
selinux.setexec(None)
if returnpid:
return mypids
- while mypids:
- pid = mypids.pop(0)
- retval = os.waitpid(pid, 0)[1]
- portage.process.spawned_pids.remove(pid)
- if retval != os.EX_OK:
- for pid in mypids:
- if os.waitpid(pid, os.WNOHANG) == (0,0):
- import signal
- os.kill(pid, signal.SIGTERM)
- os.waitpid(pid, 0)
- portage.process.spawned_pids.remove(pid)
- if retval & 0xff:
- return (retval & 0xff) << 8
- return retval >> 8
- return os.EX_OK
+ if output_pid:
+ # tee will exit when the other end of the pseudo-terminal is closed.
+ os.waitpid(output_pid, 0)
+ portage.process.spawned_pids.remove(output_pid)
+ if input_pid:
+ # cat is blocking on stdin, so it must be killed.
+ import signal
+ os.kill(input_pid, signal.SIGTERM)
+ os.waitpid(input_pid, 0)
+ portage.process.spawned_pids.remove(input_pid)
+ pid = mypids[-1]
+ retval = os.waitpid(pid, 0)[1]
+ portage.process.spawned_pids.remove(pid)
+ if retval != os.EX_OK:
+ if retval & 0xff:
+ return (retval & 0xff) << 8
+ return retval >> 8
+ return retval
def fetch(myuris, mysettings, listonly=0, fetchonly=0, locks_in_subdir=".locks",use_locks=1, try_mirrors=1):
"fetch files. Will use digest file if available."