Search code examples
pythonlinuxsubprocesspopenfile-descriptor

Subprocess opened with PIPE still reading input from terminal


Python App

My Python 3.6.8 app runs on CentOS 7.6, and:

  • Provides a GUI using Kivy 11.1.1.
  • Opens a sub-process from someone else.
  • The sub-process provides a custom shell.
  • When opened with pipes as below, the sub-process still reads from the terminal.
  • It ignores my app's commands to stdin.
  • But my app does receive output from the sub-process via the pipe.

Sub-Process

  • The sub-process controls a device.
  • It normally provides an interactive shell.
  • I need to control it from my app.
  • To launch it, I invoke a compiled binary.
  • It uses some Perl modules.
  • Its workings are unknown to me.

Popen Command

I launch the sub-process from the main thread with this command:

launch_cmd = "{} {} {} {}".format(path_of_compiled_binary, opt1, opt2, opt3)
self.myproc = subprocess.Popen(launch_cmd.split(), shell=False,
    cwd=self.testing_dir, close_fds=True, stdin=subprocess.PIPE,
    stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env,
    bufsize=1, universal_newlines=True, preexec_fn=self.ignore_sigint)

Write Command

I try to give the sub-process commands with

cmd = self.generate_cmd()
cmd = cmd + "\n"
bytes_sent = self.myproc.stdin.write(cmd)
self.myproc.stdin.flush()
  • The number bytes_sent is as expected.
  • The sub-process ignores this input.
  • But it will do the same command if entered into the terminal from which my app was launched.

File Descriptors

  • When launched as above, the sub-process eventually has these file descriptors.
  • This is at the stage when I need it to run my commands:

`

lr-x------. 1 demo demo 64 Jun  5 10:44 0 -> pipe:[58635]
l-wx------. 1 demo demo 64 Jun  5 10:44 1 -> pipe:[58636]
l-wx------. 1 demo demo 64 Jun  5 10:44 2 -> pipe:[58636]
lr-x------. 1 demo demo 64 Jun  5 10:44 3 -> /dev/null
lr-x------. 1 demo demo 64 Jun  5 10:44 4 -> path_of_compiled_binary
lr-x------. 1 demo demo 64 Jun  5 10:44 5 -> /dev/tty
l-wx------. 1 demo demo 64 Jun  5 10:44 6 -> /dev/tty
lrwx------. 1 demo demo 64 Jun  5 10:44 7 -> socket:[59509]
lrwx------. 1 demo demo 64 Jun  5 10:44 8 -> /dev/ttyACM0
lr-x------. 1 demo demo 64 Jun  5 10:44 9 -> /tmp/par-64656d6f/temp-15579/inc/lib/PDL/IO/Pic.pm

Solution

  • Sub-process reading from tty, not stdin

    The sub-process opens 2 file descriptors to /dev/tty:

    lr-x------. 1 demo demo 64 Jun  5 10:44 5 -> /dev/tty
    l-wx------. 1 demo demo 64 Jun  5 10:44 6 -> /dev/tty
    
    • @that-other-guy suggested that the sub-process reads from one of these instead of stdin.
    • Apparently 5, since its mode is read only?

    /usr/bin/script utility

    Another suggestion from @that-other-guy:

    • Use script to launch the sub-process.
    • The script utility is designed to capture a shell session into a live transcript.
    • The -c option spawns any given command instead of a shell.
    • Providing /dev/null for the file argument says to forget the transcript.

    Re-coding with script:

    launch_cmd = "{} {} {} {}".format(path_of_compiled_binary, opt1, opt2, opt3)
    script_argv = ["/usr/bin/script", "-c", launch_cmd, "/dev/null"]
    self.myproc = subprocess.Popen(script_argv, shell=False,
        cwd=self.testing_dir, close_fds=True, stdin=subprocess.PIPE,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env,
        bufsize=1, universal_newlines=True, preexec_fn=self.ignore_sigint)
    

    How it works

    • script spawns the sub-process and interacts with it in a pseudoterminal.
    • script reads stdin from the pipe and echoes it into its pseudoterminal for the sub-process to read.
    • Now it doesn't matter if the sub-process is reading the terminal on 5 instead of stdin on 0.

    This change overcame the problem. The sub-process now receives commands from the app.