无法在 PyQt4 中使用 QProcess 删除新进程的权限
Posted
技术标签:
【中文标题】无法在 PyQt4 中使用 QProcess 删除新进程的权限【英文标题】:Unable to drop privileges for a new process with QProcess in PyQt4 【发布时间】:2014-08-01 15:44:41 【问题描述】:这个问题是关于 PyQt4,但我不关心可移植性。目标系统仅限 GNU/Linux。
我有一个小程序,应该使用 kdesudo/kdesu/gtksu 或类似工具运行。
在某些时候,我需要启动一个新进程,假装是运行该命令的原始用户。
在文档(here 和 here)中,我读到我应该重写 setupChildProcess
方法。所以这是我的代码:
#!/usr/bin/python2
from os import setgroups, setuid, setgid, environ
from sys import argv, exit
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
testme = QtGui.QPushButton('\nTEST ME\n')
self.connect(testme, QtCore.SIGNAL('clicked()'), self.test)
self.setCentralWidget(testme)
def test(self):
sp = SandboxProcess()
sp.startDetached('id')
class SandboxProcess(QtCore.QProcess):
def __init__(self, *args, **kwargs):
super(SandboxProcess, self).__init__(*args, **kwargs)
env = environ.copy()
if 'SUDO_USER' in environ:
username = environ['SUDO_USER']
del env['SUDO_USER']
del env['SUDO_GID']
del env['SUDO_UID']
elif 'KDESU_USER' in environ:
username = environ['KDESU_USER']
del env['KDESU_USER']
else:
username = 'nobody'
env['USERNAME'] = username
env['LOGNAME'] = username
env['USER'] = username
env['MAIL'] = '/var/mail/' + username
env['HOME'] = '/home/' + username
qenv = QtCore.QProcessEnvironment()
for k, v in env.iteritems():
qenv.insert(k, v)
self.setProcessEnvironment(qenv)
def setupChildProcess(self):
super(SandboxProcess, self).setupChildProcess()
if 'SUDO_USER' in environ:
gid = environ['SUDO_GID']
uid = environ['SUDO_UID']
elif 'KDESU_USER' in environ:
gid = environ['KDE_SESSION_UID']
uid = environ['KDE_SESSION_UID']
else:
uid = 65534
gid = 65534
setgroups([])
setgid(int(gid))
setuid(int(uid))
if __name__ == '__main__':
app = QtGui.QApplication(argv)
main = MainWindow()
main.show()
exit(app.exec_())
首先,我知道我没有以最好的方式清理环境,但这只是向您展示我的问题的概念证明。
我的问题是该命令仍以 root 身份执行。此外,似乎根本没有调用setupChildProcess
。
我做错了什么?
【问题讨论】:
你试过直接调用 sp.setupChildProcess() 吗? 是的,但是这样它也会改变 parent 进程的 uid。同样在文档中它说 setupChildProcess 是自动调用的。无论如何,谢谢您的评论。 我建议在进程运行前调用setProcessEnvironment();为什么 setupChildProcess() 没有被自动执行我不知道,但我很确定应该首先设置 setProcessEnvironment() 所指的环境。 【参考方案1】:我自己找到了答案。我会在这里发布以供将来参考。
只有当你使用startDetached
而不是start
时才存在这个问题中描述的问题。
原因是startDetached
被实现为父类的静态方法,所以它永远不会看到我对setupChildProcess
的覆盖。
为了避免这种情况,您应该首先使用start
启动进程,然后应该将其分离。不幸的是,正如您所看到的 here,目前这是不可能的。
我的解决方法是使用另一个脚本作为分离进程的“代理”:
detach.py:
#!/usr/bin/env python2
from sys import argv
from os import devnull
from subprocess import Popen
with open(devnull, "w") as fnull:
Popen(argv[1:], stdout = fnull, stderr = fnull, close_fds=True)
example.py:
#!/usr/bin/python2
from os import setgroups, setuid, setgid, environ
from sys import argv, exit
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
testme = QtGui.QPushButton('\nTEST ME\n')
self.connect(testme, QtCore.SIGNAL('clicked()'), self.test)
self.setCentralWidget(testme)
def test(self):
sp = SandboxProcess()
sp.start('python2 ./detach.py firefox')
sp.waitForFinished()
print str(sp.readAllStandardOutput()).strip()
class SandboxProcess(QtCore.QProcess):
def __init__(self, *args, **kwargs):
super(SandboxProcess, self).__init__(*args, **kwargs)
env = environ.copy()
if 'SUDO_USER' in environ:
username = environ['SUDO_USER']
del env['SUDO_USER']
del env['SUDO_GID']
del env['SUDO_UID']
elif 'KDESU_USER' in environ:
username = environ['KDESU_USER']
del env['KDESU_USER']
else:
username = 'nobody'
env['USERNAME'] = username
env['LOGNAME'] = username
env['USER'] = username
env['MAIL'] = '/var/mail/' + username
env['HOME'] = '/home/' + username
#cleaning environment....
qenv = QtCore.QProcessEnvironment()
for k, v in env.iteritems():
qenv.insert(k, v)
self.setProcessEnvironment(qenv)
def setupChildProcess(self):
super(SandboxProcess, self).setupChildProcess()
if 'SUDO_USER' in environ:
gid = environ['SUDO_GID']
uid = environ['SUDO_UID']
elif 'KDESU_USER' in environ:
gid = environ['KDE_SESSION_UID']
uid = environ['KDE_SESSION_UID']
else:
uid = 65534
gid = 65534
setgroups([])
setgid(int(gid))
setuid(int(uid))
if __name__ == '__main__':
app = QtGui.QApplication(argv)
main = MainWindow()
main.show()
exit(app.exec_())
根据@mdurant 的建议,我还把与环境相关的代码移到了__init__
而不是setupChildProcess
中。
【讨论】:
以上是关于无法在 PyQt4 中使用 QProcess 删除新进程的权限的主要内容,如果未能解决你的问题,请参考以下文章
仅当 QProcess Stdout 包含子字符串时才打印它