Подпроцесс, загрязняющий родительский терминал при использовании pty - PullRequest
0 голосов
/ 18 марта 2020

Пример

Я заметил такое поведение с приложением cli ngrok . Это специально для этого примера, потому что он загрязняет родительский терминал процесса. Его основная функциональность не важна.

Получение исполняемого файла:

wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip
# There is `ngrok` executable in this dir now

Код, который создает проблему:

# ngrok_python.py
# It's in the same dir as the ngrok executable
import pty
import subprocess
import shlex
import time
import sys

pty_master, pty_slave = pty.openpty()
ngrok_cmd = "./ngrok http 80" 
# This doesn't happen for others
# ngrok_cmd = "ls -la" 

ngrok = subprocess.Popen(shlex.split(ngrok_cmd), stdin=pty_slave, stdout=pty_slave, stderr=pty_slave)

# It also won't pollute the current terminal when redirected to DEVNULL
# ngrok = subprocess.Popen(shlex.split(ngrok_cmd), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

print("The subprocess is attached to the pseudo terminal")
print("Its output should not be printed here")
print("Unless I decide to read from pty_master")

print("Checking a few times if the subprocess is done")

for i in range(3):
    time.sleep(1)
    if ngrok.poll is not None:
        print("Subprocess finished")
        sys.exit()

print("Don't want to wait any longer.")
# Running the command
python3 ngrok_python.py

Ожидается поведение

  • Единственный вывод будет из операторов печати
  • subprocess имеет пользовательский stdout/err/in. У него нет способа получить доступ к главному терминалу
  • Из-за pty, если бы я хотел прочитать, что происходит в подпроцессе, я бы прочитал его с pty_master

Фактическое поведение

  • Вывод подпроцесса ./ngrok http 80 потребляет терминал

Странная часть - запуск закомментированных частей (ngrok_cmd = "ls -la" или subprocess with subprocess.DEVNULL) приводит к ожидаемому поведению.

Вопросы

  1. Почему дочерний процесс знает, как получить доступ к родительскому терминалу, если stdout/err/in ребенок был предоставлен, изменились?
  2. Как преодолеть это в python?

1 Ответ

1 голос
/ 19 марта 2020

Я не хочу запускать исполняемый файл, загруженный с какого-то случайного сайта, но готов поспорить, что ngrok явно открывается и пишет в /dev/tty, чтобы представить информацию о его соединении.

Устройство /dev/tty относится к «управляющему терминалу» процесса, который является одной из вещей, которые процесс наследует от своего родителя. Переназначение дочерних элементов stdin, stdout и stderr не влияет на его управляющий терминал. Таким образом, в этом случае дочерний элемент сохраняет тот же управляющий терминал, что и родительский элемент, и когда дочерний элемент открывает и записывает в /dev/tty, вывод идет прямо на экран родителя, не проходя через дочерний stdout или stderr или ваш псевдо -терминал.

Чтобы добиться того, что вы ищете, вам необходимо отделить ребенка от управляющего терминала родителя и установить sh подчиненный конец псевдотерминала в качестве управляющего терминала ребенка. Это включает вызовы setsid и / или setpgrp, некоторую манипуляцию дескриптора файла и, возможно, некоторые другие движения. Все это обрабатывается login_tty, если вы работаете в C.

Хорошая новость заключается в том, что в модуле Python pty есть метод, который выполняет все эти функции для вы. Этот метод pty.spawn. Он доступен в Python 2 и 3. Я связался с документацией Python 3, потому что она намного, намного лучше и включает в себя пример программы. pty.spawn в основном ведет себя как комбинация fork, exec, openpty и login_tty.

Если вы переделываете свою программу, чтобы использовать pty.spawn для запуска ngrok, тогда я почти уверен, что вы получите то поведение, которое ищете.

...