我该如何为我在pty下运行的进程设置终端前台进程组?

7
我写了一个简单的包装脚本,用于在命令失败时重复执行,名为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(),但在pty fd上会变成IOCTL,导致ptys出现-EINVAL错误。如果可能的话,我更喜欢继续使用Python subprocess机制,否则是否需要手动fork/execve?

1个回答

11
我相信如果你不需要为子进程提供一个全新的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()调用会在同一会话中创建一个新的进程组,这样新的进程将接收用户的任何ctrl-c/ctrl-z等信号,而您的重试脚本则不会。然后,tcsetpgrp()使新的进程组成为控制tty上的前台进程组。当这种情况发生时,新进程会收到一个SIGTTOU信号(因为自从setpgrp()以来,它一直处于后台进程组中),通常会使进程停止,所以忽略SIGTTOU是必要的。我们将SIGTTOU处理程序设置回之前的值,以最小化子进程被意外信号表混淆的机会。

由于子进程现在是tty的前台进程组,它的tcgetpgrp()和getpgrp()将是相同的,并且isatty(1)将为true(假设它继承自retry.py的stdout确实是tty)。您无需在子进程和tty之间代理流量,这使您可以放弃所有的select事件处理和fcntl非阻塞设置。


我尝试了一下,但没有任何效果:
retry.py -v -- ~/mysrc/retry.git/tty_tests 命令是['/home/ajb/mysrc/retry.git/tty_tests'] isatty报告为1 pgrps为28268和-1 子进程已完成:rc = 0 共运行了1次命令
- stsquad
哦!我刚注意到你在问题中给了一个 retry.py 的链接。我原以为那只是 StackOverflow 试图提供帮助,并将类似主机名的东西变成了链接。我会看一下的。 - the paul
os.tcsetgrp和os.setpgrp都返回了None,看起来它们都失败了 :-/ - stsquad
虽然我不确定在什么情况下会调用retry.py。但通常它是在终端上运行的东西。 - stsquad
4
become_tty_fg函数在末尾添加os.close(tty)可以稍微改进一下。否则,文件描述符tty仍然会保持打开状态,尽管在当前代码中这并不重要,但该函数可能会在其他地方使用。 - Pavel Shishpor
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接