在 Python 中覆盖基本信号(SIGINT、SIGQUIT、SIGKILL??)
我正在编写一个程序,根据我们公司的政策添加普通的 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:
只是再次调用该函数。这样,该函数将在发生 KeyBoardInterrupt
或 EOFError
事件时重新调用自身,直到关键代码完成。 我不认为我会遇到太多麻烦,因为我只捕获用户提供的退出命令,即使这样,也只能捕获两到三行代码。理论上,如果这些异常能够足够快地引发,我想我可能会得到“超出最大递归深度”错误,但这似乎很遥远。
还有其他问题吗?
伪代码:
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)
I'm writing a program that adds normal UNIX accounts (i.e. modifying /etc/passwd, /etc/group, and /etc/shadow) according to our corp's policy. It also does some slightly fancy stuff like sending an email to the user.
I've got all the code working, but there are three pieces of code that are very critical, which update the three files above. The code is already fairly robust because it locks those files (ex. /etc/passwd.lock), writes to to a temporary files (ex. /etc/passwd.tmp), and then, overwrites the original file with the temporary. I'm fairly pleased that it won't interefere with other running versions of my program or the system useradd, usermod, passwd, etc. programs.
The thing that I'm most worried about is a stray ctrl+c, ctrl+d, or kill command in the middle of these sections. This has led me to the signal module, which seems to do precisely what I want: ignore certain signals during the "critical" region.
I'm using an older version of Python, which doesn't have signal.SIG_IGN, so I have an awesome "pass" function:
def passer(*a):
pass
The problem that I'm seeing is that signal handlers don't work the way that I expect.
Given the following test code:
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
The problem with this code is that a ^C (SIGINT) during the time.sleep(10) call causes that function to stop, and then, my signal handler takes over as desired. However, that doesn't solve my "critical" region problem above because I can't tolerate whatever statement encounters the signal to fail.
I need some sort of signal handler that will just completely ignore SIGINT and SIGQUIT.
The Fedora/RH command "yum" is written is Python and does basically exactly what I want. If you do a ^C while it's installing anything, it will print a message like "Press ^C within two seconds to force kill." Otherwise, the ^C is ignored. I don't really care about the two second warning since my program completes in a fraction of a second.
Could someone help me implement a signal handler for CPython 2.3 that doesn't cause the current statement/function to cancel before the signal is ignored?
As always, thanks in advance.
Edit: After S.Lott's answer, I've decided to abandon the signal module.
I'm just going to go back to try: except:
blocks. Looking at my code there are two things that happen for each critical region that cannot be aborted: overwriting file with file.tmp and removing the lock once finished (or other tools will be unable to modify the file, until it is manually removed). I've put each of those in their own function inside a try:
block, and the except:
simply calls the function again. That way the function will just re-call itself in the event of KeyBoardInterrupt
or EOFError
, until the critical code is completed.
I don't think that I can get into too much trouble since I'm only catching user provided exit commands, and even then, only for two to three lines of code. Theoretically, if those exceptions could be raised fast enough, I suppose I could get the "maximum reccurrsion depth exceded" error, but that would seem far out.
Any other concerns?
Pesudo-code:
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)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
没有真正的方法可以让你的脚本真正保存。当然,您可以忽略信号并使用
try: except:
捕获键盘中断,但这取决于您的应用程序对此类中断的幂等性,并且它必须能够在处理中断后恢复操作某种保存点。您唯一真正可以做的就是处理临时文件(而不是原始文件),并在完成工作后将它们移动到最终目的地。我认为从文件系统的角度来看,此类文件操作应该是“原子的”。否则,如果发生中断:使用干净的数据重新开始处理。
There is no real way to make your script really save. Of course you can ignore signals and catch a keyboard interrupt using
try: except:
but it is up to your application to be idempotent against such interrupts and it must be able to resume operations after dealing with an interrupt at some kind of savepoint.The only thing that you can really to is to work on temporary files (and not original files) and move them after doing the work into the final destination. I think such file operations are supposed to be "atomic" from the filesystem prospective. Otherwise in case of an interrupt: restart your processing from start with clean data.