From ef0055e5e245d331de48a62c42a85d2886d335b1 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Wed, 14 Dec 2016 10:08:44 +0100 Subject: fix SIGWINCH race condition during startup Some programs (e.g. ncmpc) do not like receiving a terminal of unknown size. Before we issue the resizepty method that would be the case. Since there was no synchronisation between resizepty and execvp, a program that starts quickly could run into this situation. Now we ensure that resizepty is called before execvp using an extra pipe. --- tcvt.py | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/tcvt.py b/tcvt.py index 0e1a528..a257c2b 100755 --- a/tcvt.py +++ b/tcvt.py @@ -732,32 +732,58 @@ class ForkPty(object): self.environ = environ self.pid = -1 self.masterfd = -1 + self.startpipew = -1 + self.errpiper = -1 self.exitcode = 255 def __enter__(self): assert self.pid == -1 assert self.masterfd == -1 - errpiper, errpipew = os.pipe() + assert self.startpipew == -1 + assert self.errpiper == -1 + startpiper, self.startpipew = os.pipe() + self.errpiper, errpipew = os.pipe() set_cloexec(errpipew) self.pid, self.masterfd = pty.fork() if self.pid == 0: # child - os.close(errpiper) + os.close(self.startpipew) + os.close(self.errpiper) os.environ.update(self.environ) + # wait for the parent + os.read(startpiper, 1) + os.close(startpiper) try: os.execvp(self.argv[0], self.argv) except OSError as err: os.write(errpipew, "exec failed: %s" % (err,)) sys.exit(255) + os.close(startpiper) os.close(errpipew) - data = os.read(errpiper, 1024) - os.close(errpiper) + return self.masterfd + + def start(self): + """Allow the process to start executing. + @raises ExecutionError: when execvp in the child fails + """ + assert self.startpipew >= 0 + assert self.errpiper >= 0 + # signal that execvp can proceed + os.write(self.startpipew, b"\0") + os.close(self.startpipew) + self.startpipew = -1 + # check for execvp errors + data = os.read(self.errpiper, 1024) + os.close(self.errpiper) + self.errpiper = -1 if data: raise ExecutionError(data) - return self.masterfd - def __exit__(self, *_): + assert self.pid > 0 + assert self.masterfd >= 0 + assert self.startpipew == -1 + assert self.errpiper == -1 os.close(self.masterfd) status = os.waitpid(self.pid, 0)[1] if status & 0xff == 0: # not killed by a signal @@ -779,6 +805,7 @@ def main(): with process as masterfd: with Terminal(acsc, options.columns, reverse=options.reverse) as t: t.resizepty(masterfd) + process.start() while True: timeout = 0 if t.refresh_needed() else None try: -- cgit v1.2.3