QProcess/Unix: don't close the childStartedPipe too soon
We were accidentally closing it along with all the other file descriptors if the CloseFileDescriptors flag was active. That meant we were unable to report start problems back to the parent process. So instead of closing everything immediately, we simply mark everything as FD_CLOEXEC so they'll be closed by execve()'s success. Because we're using CLOSE_RANGE_CLOEXEC now to detect the system call, we don't need the configure-time test. [ChangeLog][QtCore][QProcess] Fixed a bug that caused QProcess not to report start failures if the UnixProcessFlag::CloseFileDescriptors flag was active. Pick-to: 6.8 Change-Id: I4d81d763281354e886d9fffd56ef6ab8b6115715 Reviewed-by: Alexey Edelev <alexey.edelev@qt.io> Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io> Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de> (cherry picked from commit 3d007ff2e9caf328f92d65f71a94fd869552b1b6)
This commit is contained in:
parent
b2191e4450
commit
01010851cb
@ -104,18 +104,6 @@ clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
}
|
||||
")
|
||||
|
||||
# close_range
|
||||
qt_config_compile_test(close_range
|
||||
LABEL "close_range()"
|
||||
CODE
|
||||
"#include <unistd.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
return close_range(3, 1024, 0) != 0;
|
||||
}
|
||||
")
|
||||
|
||||
# cloexec
|
||||
qt_config_compile_test(cloexec
|
||||
LABEL "O_CLOEXEC"
|
||||
@ -610,11 +598,6 @@ qt_feature("clock-monotonic" PUBLIC
|
||||
CONDITION QT_FEATURE_clock_gettime AND TEST_clock_monotonic
|
||||
)
|
||||
qt_feature_definition("clock-monotonic" "QT_NO_CLOCK_MONOTONIC" NEGATE VALUE "1")
|
||||
qt_feature("close_range" PRIVATE
|
||||
LABEL "close_range()"
|
||||
CONDITION QT_FEATURE_process AND TEST_close_range
|
||||
AUTODETECT UNIX
|
||||
)
|
||||
qt_feature("doubleconversion" PRIVATE
|
||||
LABEL "DoubleConversion"
|
||||
)
|
||||
|
@ -43,10 +43,6 @@
|
||||
#if __has_include(<paths.h>)
|
||||
# include <paths.h>
|
||||
#endif
|
||||
#if __has_include(<linux/close_range.h>)
|
||||
// FreeBSD's is in <unistd.h>
|
||||
# include <linux/close_range.h>
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(process)
|
||||
#include <forkfd.h>
|
||||
@ -846,10 +842,13 @@ static const char *applyProcessParameters(const QProcess::UnixProcessParameters
|
||||
if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseFileDescriptors)) {
|
||||
int r = -1;
|
||||
int fd = qMax(STDERR_FILENO + 1, params.lowestFileDescriptorToClose);
|
||||
#if QT_CONFIG(close_range)
|
||||
#ifdef CLOSE_RANGE_CLOEXEC
|
||||
// Mark the file descriptors for closing upon execve() - we delay
|
||||
// closing so we don't close the ones QProcess needs for itself.
|
||||
// On FreeBSD, this probably won't fail.
|
||||
// On Linux, this will fail with ENOSYS before kernel 5.9.
|
||||
r = close_range(fd, INT_MAX, 0);
|
||||
// On Linux, this will fail with ENOSYS before kernel 5.9 and EINVAL
|
||||
// before 5.11.
|
||||
r = close_range(fd, INT_MAX, CLOSE_RANGE_CLOEXEC);
|
||||
#endif
|
||||
if (r == -1) {
|
||||
// We *could* read /dev/fd to find out what file descriptors are
|
||||
@ -860,7 +859,7 @@ static const char *applyProcessParameters(const QProcess::UnixProcessParameters
|
||||
if (struct rlimit limit; getrlimit(RLIMIT_NOFILE, &limit) == 0)
|
||||
max_fd = limit.rlim_cur;
|
||||
for ( ; fd < max_fd; ++fd)
|
||||
close(fd);
|
||||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,8 @@ private slots:
|
||||
void startWithOldOpen();
|
||||
void execute();
|
||||
void startDetached();
|
||||
void simpleStartFail_data();
|
||||
void simpleStartFail();
|
||||
void crashTest();
|
||||
void crashTest2();
|
||||
void echoTest_data();
|
||||
@ -105,8 +107,8 @@ private slots:
|
||||
void switchReadChannels();
|
||||
void discardUnwantedOutput();
|
||||
void setWorkingDirectory();
|
||||
void setNonExistentWorkingDirectory_data();
|
||||
void setNonExistentWorkingDirectory();
|
||||
void detachedSetNonExistentWorkingDirectory();
|
||||
|
||||
void exitStatus_data();
|
||||
void exitStatus();
|
||||
@ -370,6 +372,62 @@ void tst_QProcess::startDetached()
|
||||
QCOMPARE(QProcess::startDetached("nonexistingexe"), false);
|
||||
}
|
||||
|
||||
void tst_QProcess::simpleStartFail_data()
|
||||
{
|
||||
QTest::addColumn<bool>("detached");
|
||||
QTest::addColumn<bool>("unixCloseFileDescriptors");
|
||||
|
||||
QTest::addRow("normal") << false << false;
|
||||
QTest::addRow("detached") << true << false;
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// make sure UnixProcessFlag::CloseFileDescriptors doesn't affect our
|
||||
// error reporting
|
||||
QTest::addRow("normal+closefds") << false << true;
|
||||
QTest::addRow("detached+closefds") << true << true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QProcess::simpleStartFail()
|
||||
{
|
||||
// for more complex and stressful cases, see the other failToStart* tests
|
||||
QFETCH(bool, detached);
|
||||
QProcess process;
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QFETCH(bool, unixCloseFileDescriptors);
|
||||
if (unixCloseFileDescriptors)
|
||||
process.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
|
||||
QSignalSpy stateSpy(&process, &QProcess::stateChanged);
|
||||
QSignalSpy errorOccurredSpy(&process, &QProcess::errorOccurred);
|
||||
|
||||
process.setProgram(nonExistentFileName);
|
||||
|
||||
if (detached) {
|
||||
qint64 pid = -1;
|
||||
QVERIFY(!process.startDetached(&pid));
|
||||
QCOMPARE(pid, -1);
|
||||
} else {
|
||||
process.start();
|
||||
QVERIFY(!process.waitForFinished());
|
||||
|
||||
QCOMPARE(stateSpy.size(), 2);
|
||||
QCOMPARE(stateSpy[0][0].value<QProcess::ProcessState>(), QProcess::Starting);
|
||||
QCOMPARE(stateSpy[1][0].value<QProcess::ProcessState>(), QProcess::NotRunning);
|
||||
}
|
||||
|
||||
QCOMPARE(errorOccurredSpy.size(), 1);
|
||||
QCOMPARE(process.error(), QProcess::FailedToStart);
|
||||
QCOMPARE_NE(process.errorString(), "Unknown error");
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QVERIFY2(process.errorString().contains(": execve: "), process.errorString().toLocal8Bit());
|
||||
QVERIFY2(process.errorString().contains(qt_error_string(ENOENT)), process.errorString().toLocal8Bit());
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QProcess::readFromProcess()
|
||||
{
|
||||
QProcess *process = qobject_cast<QProcess *>(sender());
|
||||
@ -2839,49 +2897,46 @@ void tst_QProcess::setWorkingDirectory()
|
||||
QCOMPARE(QDir(m_temporaryDir.path()).canonicalPath(), QDir(workingDir.constData()).canonicalPath());
|
||||
}
|
||||
|
||||
void tst_QProcess::setNonExistentWorkingDirectory_data()
|
||||
{
|
||||
simpleStartFail_data();
|
||||
}
|
||||
|
||||
void tst_QProcess::setNonExistentWorkingDirectory()
|
||||
{
|
||||
QFETCH(bool, detached);
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(nonExistentFileName);
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QFETCH(bool, unixCloseFileDescriptors);
|
||||
if (unixCloseFileDescriptors)
|
||||
process.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
|
||||
QSignalSpy stateSpy(&process, &QProcess::stateChanged);
|
||||
QSignalSpy errorOccurredSpy(&process, &QProcess::errorOccurred);
|
||||
|
||||
// use absolute path because on Windows, the executable is relative to the parent's CWD
|
||||
// while on Unix with fork it's relative to the child's (with posix_spawn, it could be either).
|
||||
process.start(QFileInfo("testSetWorkingDirectory/testSetWorkingDirectory").absoluteFilePath());
|
||||
|
||||
QVERIFY(!process.waitForFinished());
|
||||
QCOMPARE(errorOccurredSpy.size(), 1);
|
||||
QCOMPARE(process.error(), QProcess::FailedToStart);
|
||||
QCOMPARE(stateSpy.size(), 2);
|
||||
QCOMPARE(stateSpy[0][0].value<QProcess::ProcessState>(), QProcess::Starting);
|
||||
QCOMPARE(stateSpy[1][0].value<QProcess::ProcessState>(), QProcess::NotRunning);
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QVERIFY2(process.errorString().startsWith("chdir:"), process.errorString().toLocal8Bit());
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QProcess::detachedSetNonExistentWorkingDirectory()
|
||||
{
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(nonExistentFileName);
|
||||
|
||||
QSignalSpy errorOccurredSpy(&process, &QProcess::errorOccurred);
|
||||
|
||||
// use absolute path because on Windows, the executable is relative to the parent's CWD
|
||||
// while on Unix with fork it's relative to the child's (with posix_spawn, it could be either).
|
||||
process.setProgram(QFileInfo("testSetWorkingDirectory/testSetWorkingDirectory").absoluteFilePath());
|
||||
|
||||
qint64 pid = -1;
|
||||
QVERIFY(!process.startDetached(&pid));
|
||||
QCOMPARE(pid, -1);
|
||||
QCOMPARE(process.error(), QProcess::FailedToStart);
|
||||
QVERIFY(process.errorString() != "Unknown error");
|
||||
if (detached) {
|
||||
qint64 pid = -1;
|
||||
QVERIFY(!process.startDetached(&pid));
|
||||
QCOMPARE(pid, -1);
|
||||
} else {
|
||||
process.start();
|
||||
QVERIFY(!process.waitForFinished());
|
||||
|
||||
QCOMPARE(stateSpy.size(), 2);
|
||||
QCOMPARE(stateSpy[0][0].value<QProcess::ProcessState>(), QProcess::Starting);
|
||||
QCOMPARE(stateSpy[1][0].value<QProcess::ProcessState>(), QProcess::NotRunning);
|
||||
}
|
||||
|
||||
QCOMPARE(errorOccurredSpy.size(), 1);
|
||||
QCOMPARE(process.error(), QProcess::FailedToStart);
|
||||
QCOMPARE_NE(process.errorString(), "Unknown error");
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QVERIFY2(process.errorString().startsWith("chdir:"), process.errorString().toLocal8Bit());
|
||||
|
Loading…
x
Reference in New Issue
Block a user