TalkingToSubprocess » History » Version 2

Chris Cannam, 2010-11-25 12:25 PM

1 1 Chris Cannam
h1. Communicating with a subprocess
2 1 Chris Cannam
3 1 Chris Cannam
h3. The problem
4 1 Chris Cannam
5 2 Chris Cannam
We want to run a subprocess (Hg) in such a way as to be able to talk to it interactively, pretending we are a terminal -- so we can deal with username/password interactions.  The subprocess has some possibly platform-dependent test for whether its input is a terminal; if not, it will not ask for credentials (failing instead).
6 2 Chris Cannam
7 2 Chris Cannam
In practice, it appears the test comes down to a call to Python's sys.stdio.isatty() function.  The documentation suggests you can override this test with e.g. --config ui.interactive=true on the command line; I'm not entirely sure.
8 2 Chris Cannam
9 2 Chris Cannam
We are using QProcess, which perhaps doesn't actually make matters any simpler to understand.
10 2 Chris Cannam
11 2 Chris Cannam
h3. Linux and OS/X
12 2 Chris Cannam
13 2 Chris Cannam
Two parts to the problem: how to give the subprocess a pseudoterminal, and how to make sure it doesn't get the controlling terminal of the parent process.  (isatty returns true if the file descriptor is a terminal or pseudoterminal.)
14 2 Chris Cannam
15 2 Chris Cannam
QProcess can set the subprocess stdin to an existing file by name.  So, we can call openpty() (nonstandard, nonrecommended BSD API) or carry out similar magic to allocate a pseudoterminal master/slave pair via /dev/ptmx or whatever, then pass the slave device name to QProcess::setStandardInputFile.
16 2 Chris Cannam
17 2 Chris Cannam
However, if our own process (the parent) was invoked from a terminal, it will already have a controlling terminal and openpty will just give us that terminal back again.  We need to detach from our controlling terminal first.
18 2 Chris Cannam
19 2 Chris Cannam
A method that seems to be recommended in some quarters is to fork, then setsid, then fork -- but we can't easily do this at the point of running the subprocess, because QProcess is handling that fork for us.  (Would execvp not be easier?)
20 2 Chris Cannam
21 2 Chris Cannam
And we can't fork/setsid/fork at the start of our own process, because although this works fine on Linux, OS/X will reject it in subsequent Core API calls ("__THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__").
22 2 Chris Cannam
23 2 Chris Cannam
So instead at startup we explicitly detach from the terminal using ioctl with TIOCNOTTY on fd 0 if isatty(0) returns true, or on the result of opening /dev/tty otherwise.  That seems to work.
24 2 Chris Cannam
25 2 Chris Cannam
h3. Windows
26 2 Chris Cannam
27 2 Chris Cannam
isatty on Windows returns true if the fd "is associated with a character device (a terminal, console, printer, or serial port)".  I'm not sure what it means by a terminal -- Windows doesn't seem to have them.  A subprocess can be created (via CreateProcess) with a console, but QProcess explicitly uses the CREATE_NO_WINDOW flag which sets no console handle on the subprocess.  In any case a console in Windows is visible as a window, which probably isn't ideal.