Как мне установить группу процессов терминала переднего плана для процесса, который я запускаю под pty?
Я написал простой скрипт-обертку для повторения команд, когда они терпят неудачу, с именем retry.py. Однако, поскольку я хочу увидеть вывод дочерней команды, мне пришлось использовать некоторые хитрости pty. Это работает нормально для таких программ, как rsync, но другие, такие как scp, применяют дополнительный тест для отображения таких вещей, как их индикатор прогресса.
У кода scp есть тест, который в широком смысле:
getpgrp() == tcgetpgrp(STDOUT_FILENO);
Что не удается, когда я запускаю через скрипт оболочки. Как вы можете видеть из моего простого тестового примера tty_test.c:
./tty_tests
isatty reports 1
pgrps are 13619 and 13619
а также:
./retry.py -v -- ./tty_tests
command is ['./tty_tests']
isatty reports 1
pgrps are 13614 and -1
child finished: rc = 0
Ran command 1 times
Я попытался использовать tcsetpgrp(), который заканчивается как IOCTL на pty fd, но в результате получается -EINVAL для ptys. Я бы предпочел продолжать использовать механизм подпроцесса Python, если это вообще возможно, или для этого потребуется ручной форк /execve?
1 ответ
Я полагаю, что вы можете сократить свою программу до этого, если вам не нужно предоставлять совершенно новый pty для подпроцесса:
from argparse import ArgumentParser
import os
import signal
import subprocess
import itertools
# your argumentparser stuff goes here
def become_tty_fg():
os.setpgrp()
hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
tty = os.open('/dev/tty', os.O_RDWR)
os.tcsetpgrp(tty, os.getpgrp())
signal.signal(signal.SIGTTOU, hdlr)
if __name__ == "__main__":
args = parser.parse_args()
if args.verbose: print "command is %s" % (args.command)
if args.invert and args.limit==None:
sys.exit("You must define a limit if you have inverted the return code test")
for run_count in itertools.count():
return_code = subprocess.call(args.command, close_fds=True,
preexec_fn=become_tty_fg)
if args.test == True: break
if run_count >= args.limit: break
if args.invert and return_code != 0: break
elif not args.invert and return_code == 0: break
print "Ran command %d times" % (run_count)
setpgrp()
call создает новую группу процессов в том же сеансе, так что новый процесс будет получать любые ctrl-c/ctrl-z/etc от пользователя, а ваш повторный скрипт не будет. Тогда tcsetpgrp()
делает новую группу процессов приоритетной на управляющем tty. Новый процесс получает SIGTTOU
когда это произойдет (так как setpgrp()
, он был в группе фоновых процессов), что обычно приводит к остановке процесса, так что причина игнорирования SIGTTOU
, Мы устанавливаем SIGTTOU
обработчик обратно к тому, что было раньше, чтобы минимизировать вероятность того, что подпроцесс будет сбит с толку неожиданной таблицей сигналов.
Поскольку подпроцесс теперь находится в группе переднего плана для tty, его tcgetpgrp() и getpgrp () будут одинаковыми, а isatty(1) будет истинным (предполагая, что stdout, который он наследует от retry.py, фактически является tty). Вам не нужно использовать прокси-трафик между подпроцессом и tty, что позволяет отключить все select
обработка событий и fcntl-nonblocking-setting.