在Ubuntu 15.10中无法终止使用python创build的sudo进程

我刚刚更新到Ubuntu 15.10,并突然在Python 2.7中,我不能终止我作为时创build的进程。 例如,这不会终止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; DRsudo不会转发由命令进程组中的进程发送的信号, 自2014年5月28日在sudo 1.8.11发布的提交 – python进程(sudo的父进程)和tcpdump进程(grandchild)处于相同进程组,因此sudo不会将.terminate()发送的SIGTERM信号转发给tcpdump进程。


它在作为root用户运行代码时以及作为常规用户+ sudo时显示相同的行为

以常规用户身份运行会引发OSError: [Errno 1] Operation not permitted .terminate()上的OSError: [Errno 1] Operation not permitted例外(如预期的那样)。

root身份运行重现了这个问题: sudotcpdump进程不会在.terminate().terminate() ,并且代码在Ubuntu 15.10的.terminate()被卡住。

这个代码杀死了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 

正如@E先生在评论中指出的那样,在这里你不需要sudo :你已经是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 ,它将信号传递给tcpdumptcpdump应该停止捕获,并且这两个进程都退出,而.communicate()tcpdump的stderr输出返回给python脚本。

注意:原则上,可以从相同的sudo(8)手册页运行该命令而不创建子进程:

作为一种特殊情况,如果策略插件没有定义关闭函数并且不需要pty, sudo将直接执行命令,而不是先调用fork(2)

因此.terminate()可以直接发送SIGTERM到tcpdump进程 – 虽然这不是解释: sudo tcpdump在我的测试中在Ubuntu 12.04和15.10上创建了两个进程。

如果我在shell中运行sudo tcpdump -w example.pcap -i eth0 -n icmp然后kill -SIGTERM终止这两个进程。 它看起来不像Python问题(Python 2.7.3(在Ubuntu 12.04上使用)在Ubuntu 15.10上表现相同,Python 3在这里也失败了)。

它与进程组( 作业控制 )有关:将preexec_fn=os.setpgrp传递preexec_fn=os.setpgrp subprocess.Popen()这样sudo将在一个新的进程组(作业)中,在shell中作为领导者,使得tcpdump_process.terminate()在这种情况下工作。

发生了什么? 它适用于以前的版本。

解释是在sudo的源代码中 :

不要转发命令进程组中进程发送的信号 ,不要转发它,因为我们不希望孩子间接自杀。 例如,这可能会发生一些重新启动的版本,调用kill(-1,SIGTERM)来终止所有其他进程。 重点是我的

preexec_fn=os.setpgrp更改了sudo的进程组。 sudo的后代如tcpdump进程继承了这个组。 pythontcpdump不再处于同一个进程组中,因此.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的错误跟踪器和/或上游错误跟踪器上打开文档问题。