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. |