Как мне установить группу процессов терминала переднего плана для процесса, который я запускаю под 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.

Другие вопросы по тегам