在Python中覆盖基本信号(SIGINT,SIGQUIT,SIGKILL??)

1 投票
1 回答
5273 浏览
提问于 2025-04-16 16:00

我正在写一个程序,用来根据我们公司的政策添加普通的UNIX账户(也就是修改/etc/passwd、/etc/group和/etc/shadow这些文件)。这个程序还做了一些稍微复杂的事情,比如给用户发送邮件。

我已经把所有代码都写好了,但有三段代码非常关键,它们负责更新上面提到的三个文件。这些代码已经相当稳健,因为它们会锁定这些文件(比如说,/etc/passwd.lock),先写入一个临时文件(比如说,/etc/passwd.tmp),然后再用临时文件覆盖原来的文件。我对这个过程很满意,因为它不会干扰到我程序的其他运行版本,或者系统的useradd、usermod、passwd等程序。

我最担心的事情是,在这些关键代码执行的过程中,可能会意外按下ctrl+c、ctrl+d,或者使用kill命令。这让我关注到了信号模块,它似乎正好可以做到我想要的:在“关键”区域内忽略某些信号。我使用的是一个较旧版本的Python,没有signal.SIG_IGN,所以我写了一个很棒的“pass”函数:

def passer(*a):
    pass

我遇到的问题是,信号处理程序的工作方式并不是我预期的那样。给定以下测试代码:

def passer(a=None, b=None):
pass

def signalhander(enable):
    signallist = (signal.SIGINT, signal.SIGQUIT, signal.SIGABRT, signal.SIGPIPE,       signal.SIGALRM, signal.SIGTERM, signal.SIGKILL)
    if enable:
        for i in signallist:
            signal.signal(i, passer)
    else:
        for i in signallist:
            signal.signal(i, abort)
    return


def abort(a=None, b=None):
    sys.exit('\nAccount was not created.\n')
    return
signalhander(True)                                                                                                                                                                                                                 
print('Enabled')
time.sleep(10)   # ^C during this sleep

这个代码的问题在于,在time.sleep(10)调用期间按下^C(SIGINT)会导致这个函数停止,然后我的信号处理程序按预期接管。然而,这并没有解决我上面提到的“关键”区域问题,因为我不能容忍遇到信号的任何语句失败。

我需要某种信号处理程序,能够完全忽略SIGINT和SIGQUIT。Fedora/RH的命令“yum”是用Python写的,基本上做的就是我想要的。如果在它安装任何东西时按下^C,它会打印一条消息,比如“在两秒内按^C强制结束。”否则,^C会被忽略。我并不在乎这个两秒的警告,因为我的程序在几分之一秒内就能完成。

有人能帮我实现一个CPython 2.3的信号处理程序吗?这个程序在信号被忽略之前不会导致当前的语句/函数被取消。

如往常一样,提前感谢大家。


编辑:在S.Lott的回答后,我决定放弃信号模块。

我打算回到try: except:块。看着我的代码,每个关键区域都有两件事情是不能中断的:用file.tmp覆盖文件和完成后移除锁(否则其他工具将无法修改文件,直到手动移除)。我把这两件事放在各自的函数里,并放在try:块中,except:则简单地再次调用这个函数。这样一来,如果发生KeyBoardInterruptEOFError,这个函数就会在关键代码完成之前不断自我调用。

我觉得这样应该不会出太多问题,因为我只是捕获用户提供的退出命令,而且就只有两到三行代码。理论上,如果这些异常能被快速触发,我想我可能会遇到“最大递归深度超出”的错误,但这似乎不太可能。

还有其他担忧吗?

伪代码:

def criticalRemoveLock(file):
    try:
        if os.path.isFile(file):
            os.remove(file)
        else:
            return True
    except (KeyboardInterrupt, EOFError):
        return criticalRemoveLock(file)
def criticalOverwrite(tmp, file):
    try:
        if os.path.isFile(tmp):
            shutil.copy2(tmp, file)
            os.remove(tmp)
        else:
            return True
     except (KeyboardInterrupt, EOFError):
        return criticalOverwrite(tmp, file)

1 个回答

3

其实没有什么办法能让你的脚本完全安全。当然,你可以忽略一些信号,或者用 try: except: 来捕捉键盘中断,但这还是得看你的应用程序能不能处理这些中断。它必须能够在遇到中断后,从某个保存点继续操作。

你能做的就是先在临时文件上工作,而不是直接在原文件上。等你完成工作后,再把这些临时文件移动到最终的位置。我觉得从文件系统的角度来看,这种文件操作应该是“原子”的。否则,如果发生中断,你就得从头开始处理,使用干净的数据。

撰写回答