从Python线程启动时,FreeBSD Jail无法启动

0 投票
2 回答
767 浏览
提问于 2025-04-17 07:56

下面的代码可以正常工作:

ezjail-admin create -f default test 10.0.0.1
ezjail-admin start test

或者用Python写:

import subprocess, shlex
command = 'ezjail-admin create -f default test 10.0.0.1'
subprocess.Popen(shlex.split(command)).wait()
command = 'ezjail-admin start test'
subprocess.Popen(shlex.split(command)).wait()

但是当在一个线程中执行(在CherryPy下),监狱(jail)安装得很好,但从未正确启动:

root    15231  0.0  0.0  8296  2056   1  I+    4:52PM   0:00.01 /bin/sh /usr/local/etc/rc.d/ezjail.sh start content
root    15240  0.0  0.0  8296  2120   1  I+    4:52PM   0:00.06 sh /etc/rc.d/jail onestart content
root    15407  0.0  0.0  8296  2016   1  I+J   4:52PM   0:00.01 /bin/sh /etc/rc
root    15467  0.0  0.0  8296  2060   1  I+J   4:52PM   0:00.00 /bin/sh /etc/rc
root    15474  0.0  0.0  6920  1224   1  I+J   4:52PM   0:00.00 /usr/sbin/syslogd -ss

监狱是启动了,但 /etc/rc 在启动syslogd后似乎就卡住了。

不知怎么的,当在一个线程中执行时,监狱的rc启动失败。我怀疑缺少某些环境设置(可能是pty?)导致jexec无法正常运行。

尝试连接控制台也失败了(实际上会启动一个监狱的独立副本),无论是用 jexec 还是 ezjail-admin console。监狱的 /var/log 里没有内容(除了在syslogd启动时创建的空日志文件),主机的日志文件也是如此。

有没有人知道为什么在非线程应用中执行的命令可以正常工作,但一旦在线程中运行,就会出现严重问题?

总结一下:当尝试从Python线程启动监狱时,rc在启动syslogd后就卡住了。同样的命令在非线程应用中可以成功启动监狱。

补充:使用fork可以正常工作……这与线程有关。

以下命令对 ezjail-admin start 不起作用。 ezjail-admin create 则完全正常。

class TestThread(threading.Thread):
  def run(self):
    command = 'ezjail-admin create -f content content 10.0.254.33'
    os.system(command)
    command = 'ezjail-admin start content'
    os.system(command)    

tt = TestThread()
tt.start()

实际代码可以在 https://github.com/masom/Puck/tree/master/client 查看。当前的实现使用了早期的fork,但使用线程会更简洁。

2 个回答

-1

os.system这个命令不会等到你输入的指令执行完毕就继续往下走。所以你在创建和启动的时候,它们是同时进行的,这样就可能导致启动失败,因为它们之间会有竞争条件。你可以在创建命令后加个睡眠时间,或者用subprocess.Popen来调用,并加上.wait()来等待。

我想说的是,wait这个命令不管用,因为ezjail-admin是一个脚本,它自己也不会等结果。

如果你想验证我说的对不对,可以试试在两个命令之间加上1秒的睡眠时间再运行。

创建监狱(jail)并不需要伪终端(pty)。其实你可以只用内存运行一个虚拟监狱,里面只包含静态链接的进程,完全不需要文件系统(不过这种方法现在在网上不太好找)...

虽然加睡眠时间有点麻烦,但通过jls命令检查正在运行的监狱是个办法(ezjail也会在这里注册,因为它是统一的)。

-1

你真的想在这里用popen吗?你是想读取标准输出(stdout)或标准错误(stderr),还是想写入标准输入(stdin)?从.wait()来看,似乎并不是这样。如果是这样,为什么还要用popen呢?

我对Python不太了解,所以我假设subprocess.Popen是基于popen(3)来实现的。

在内部,popen需要进行进程分叉和执行,所以我怀疑你真的有线程问题。因为你在使用popen,更可能的是某个东西在尝试使用与父进程连接的管道时被阻塞了。添加线程会改变Python处理输入输出的方式,这可能会暴露出问题。

所以,为了测试这个,你可以尝试同样的命令,但把标准输入、标准输出和标准错误都重定向到/dev/null。使用这个命令:

sh -c 'ezjail-admin start test < /dev/null > /dev/null 2> /dev/null'

ezjail的文档可能会告诉你它是否对标准输入输出有处理。如果没有,底层的jail(2)调用会使用继承的句柄,考虑到你的代码,我很确定你想要关闭这些句柄,或者把它们重定向到某个有用的地方。

更新:

如果这个命令在标准句柄重定向到/dev/null时能正常工作,那么问题就和你处理标准输入输出句柄的方式有关,而不是线程。你需要决定这些句柄要去哪里,并在启动jail时正确设置它们。如果你打算读取和/或写入连接到jail子进程的管道,你需要注意你在评论中提到的文档页面上的大红警告,以避免死锁。这几乎肯定是你遇到的问题。

你提到ssh和服务器进程,但不太清楚你真正想表达什么。你是在创建一个jail,然后在这个jail中执行/etc/rc。这个进程会从它的父进程继承标准输入输出句柄,并设置一个系统环境。构成系统环境的进程也会继承这些句柄并使用它们。系统的行为会根据这些句柄指向的地方而有所不同。在jail中调用/etc/rc的不同方法会改变这些句柄。要让它和“线程”一起工作,只需正确设置这些句柄即可。

撰写回答