无法在 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正常退出

仅当 QProcess Stdout 包含子字符串时才打印它

使用 QProcess 时无法读取命令的输出

PyQt4:使用 QPushButton 小部件从 QList 小部件中删除项目

无法在 ipython 中完全卸载 pyqt4

在ubuntu服务中使用pyQT4运行python文件时无法连接到服务器