在Ubuntu 15.10中无法终止使用python创建的sudo进程
问题内容:
我刚刚更新到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
-蟒处理(须藤的父)和tcpdump的处理(孙子)是通过默认,因此相同的处理组中的sudo
不将SIGTERM
发送的信号转发.terminate()
给tcpdump
进程。
在以root用户和普通用户+ sudo身份运行该代码时,它显示出相同的行为
以正常用户身份运行会引发OSError: [Errno 1] Operation not permitted
异常.terminate()
(如预期)。
以as身份运行会root
重现该问题:sudo
并tcpdump
不会终止进程,.terminate()
并且代码仍停留.communicate()
在Ubuntu
15.10上。
相同的代码会在Ubuntu 12.04上杀死这两个进程。
tcpdump_process
名称具有误导性,因为变量引用的是sudo
进程(子进程),而不是tcpdump
(孙子进程):
python
└─ sudo tcpdump -w example.pcap -i eth0 -n icmp
└─ tcpdump -w example.pcap -i eth0 -n icmp
正如@ Mr.E在评论中指出的那样,您不需要sudo
在这里:您已经是root用户了(尽管您不应该是root用户-
您可以在没有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
应停止捕获的信号中继到SIGTERM
,两个进程都退出,.communicate()
并将tcpdump
的stderr输出返回到python脚本。
注意:原则上,可以从同一sudo(8)手册页运行命令而无需创建子进程:
在特殊情况下,如果策略插件未定义关闭函数且不需要pty,
sudo
则将直接执行命令,而不是先调用fork(2)
因此.terminate()
可能会tcpdump
直接将SIGTERM发送给该进程-尽管不是解释原因:sudo tcpdump
在我的测试中,同时在Ubuntu 12.04和15.10上创建了两个进程。
如果我sudo tcpdump -w example.pcap -i eth0 -n icmp
在外壳中运行,则将kill -SIGTERM
终止两个进程。它看起来不像Python问题(Python 2.7.3(在Ubuntu 12.04上使用)在Ubuntu
15.10上的行为相同。Python3也在此处失败)。
它与流程组(作业控制)有关:传递preexec_fn=os.setpgrp
给subprocess.Popen()
这样,sudo
它将进入一个新的流程组(作业),在该流程组中,它是领导者,如tcpdump_process.terminate()
在这种情况下在shell中进行工作。
发生了什么?它适用于以前的版本。
解释在sudo的源代码中:
不要转发命令的进程组中某个进程发送的信号
,也不要转发该信号,因为我们不希望孩子间接杀死自己。例如,某些版本的重新引导会调用kill(-1,SIGTERM)杀死所有其他进程,这可能会发生。重点是我的
preexec_fn=os.setpgrp
更改sudo
的流程组。sudo
的子孙(例如tcpdump
process)继承了该组。python
并且tcpdump
不再位于同一个进程组中,因此发送的信号.terminate()
被中继sudo
到tcpdump
并退出。
Ubuntu 15.04使用问题Sudo version 1.8.9p5
中的代码按原样工作。
Ubuntu 15.10使用Sudo version 1.8.12
包含提交的内容。
wily(15.10)中的sudo(8)手册页仍然只谈到子进程本身,而没有提及进程组:
作为一种特殊情况,sudo不会中继正在运行的命令发送的信号。
应该改为:
作为特殊情况,sudo不会中继正在运行的命令的进程组中的某个进程发送的信号。
您可以在Ubuntu的错误跟踪器和/或上游错误跟踪器上打开一个文档问题。