我刚刚更新到Ubuntu 15.10并突然在Python 2.7中我无法终止我在创建root时创建的进程.例如,这不会终止tcpdump:
import subprocess, shlex, time tcpdump_command = "sudo tcpdump -w example.pcap -i eth0 -n icmp" tcpdump_process = subprocess.Popen( shlex.split(tcpdump_command), stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(1) tcpdump_process.terminate() tcpdump_out, tcpdump_err = tcpdump_process.communicate()
发生了什么?它适用于以前的版本.
TL; DR:sudo
不转发自2014年5月28日发布的命令进程组中的进程发送的信号sudo 1.8.11
- python进程(sudo的父进程)和tcpdump进程(grandchild)默认位于同一进程组中,因此sudo
不会将SIGTERM
发送的信号转发.terminate()
给tcpdump
进程.
当作为root用户运行该代码并且作为常规用户+ sudo时,它显示相同的行为
以普通用户身份运行会引发OSError: [Errno 1] Operation not permitted
异常.terminate()
(如预期的那样).
运行时会root
重现问题:sudo
并且tcpdump
不会终止进程.terminate()
并且代码会停留.communicate()
在Ubuntu 15.10上.
相同的代码会杀死Ubuntu 12.04上的两个进程.
tcpdump_process
name是误导性的,因为变量引用sudo
进程(子进程),而不是tcpdump
(孙进程):
python ?? sudo tcpdump -w example.pcap -i eth0 -n icmp ?? tcpdump -w example.pcap -i eth0 -n icmp
正如@E先生在评论中指出的那样,你不需要sudo
在这里:你已经是根(虽然你不应该 - 你可以在没有root的情况下嗅探网络).如果你掉下来sudo
; .terminate()
作品.
通常,.terminate()
不会递归地杀死整个进程树,因此预计孙子进程会幸存.虽然sudo
是一个特例,但是从sudo(8)手册页:
当命令作为
sudo
进程的子进程运行时,sudo
会将 收到的信号中继到命令.重点是我的
即,sudo
应该传达SIGTERM
到tcpdump
和tcpdump
应该停止抓包SIGTERM
,tcpdump的距离(8)手册页:
Tcpdump将继续捕获数据包,直到被SIGINT信号(例如,通过键入中断字符,通常是control-C生成)或SIGTERM信号(通常使用kill(1)命令生成)中断为止. ;
即,预期的行为是:tcpdump_process.terminate()
发送SIGTERM,继续捕获应该停止捕获sudo
的信号,tcpdump
并且两个进程都退出并将stderr输出.communicate()
返回tcpdump
到python脚本.
注意:原则上,可以在不创建子进程的情况下运行命令,来自同一个sudo(8)手册页:
作为特殊情况,如果策略插件没有定义close函数并且不需要pty,
sudo
则将直接执行命令而不是首先调用fork(2)
因此.terminate()
可以tcpdump
直接将SIGTERM发送到进程 - 虽然它不是解释:sudo tcpdump
在我的测试中在Ubuntu 12.04和15.10上创建两个进程.
如果我sudo tcpdump -w example.pcap -i eth0 -n icmp
在shell中运行,则kill -SIGTERM
终止两个进程.它看起来不像Python问题(Python 2.7.3(在Ubuntu 12.04上使用)在Ubuntu 15.10上表现相同.Python 3也在这里失败).
它是处理组(相关作业控制):通过preexec_fn=os.setpgrp
对subprocess.Popen()
使sudo
会在它的领导者在shell一个新的进程组(作业),使tcpdump_process.terminate()
工作在这种情况下.
发生了什么?它适用于以前的版本.
解释是在sudo的源代码中:
不要转发命令进程组中进程发送的信号,不要转发它,因为我们不希望子进程间接自杀.例如,某些版本的reboot可以调用kill(-1,SIGTERM)来杀死所有其他进程.重点是我的
preexec_fn=os.setpgrp
变更sudo
的过程组.sudo
像tcpdump
进程这样的后代继承了这个组.python
和tcpdump
是相同的处理组中不再并因此通过发送的信号.terminate()
是通过中继sudo
到tcpdump
和它退出.
Ubuntu 15.04使用Sudo version 1.8.9p5
问题代码的工作原理.
Ubuntu 15.10使用Sudo version 1.8.12
包含提交的内容.
sudo(8)wily中的手册页(15.10)仍然只讨论子进程本身 - 没有提到进程组:
作为一种特殊情况,sudo不会中继由其运行的命令发送的信号.
它应该是:
作为一种特殊情况,sudo不会中继由正在运行的命令的进程组中的进程发送的信号.
您可以在Ubuntu的错误跟踪器和/或上游错误跟踪器上打开文档问题.