forkfd: fix forkfd_wait when FFD_USE_FORK was active

If we detected that the OS supports a version of system forkfd (Linux
pidfd, FreeBSD procdesc), the forkfd_wait() function was using only the
system waiting implementation, which of course can't work for file
descriptors created with FFD_USE_FORK. So just detect EBADF and attempt
again.

If the file descriptor is neither one of our pipes nor a system forkfd,
bad things will happen...

Fixes: QTBUG-82351
Change-Id: I4e559af2a9a1455ab770fffd15f59fb3160b22eb
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
This commit is contained in:
Thiago Macieira 2020-02-21 20:40:35 -08:00 committed by Edward Welbourne
parent 4605583fec
commit ba5e2ce49a
2 changed files with 79 additions and 5 deletions

View File

@ -29,6 +29,11 @@
#include "forkfd.h"
/* Macros fine-tuning the build: */
//#define FORKFD_NO_FORKFD 1 /* disable the forkfd() function */
//#define FORKFD_NO_SPAWNFD 1 /* disable the spawnfd() function */
//#define FORKFD_DISABLE_FORK_FALLBACK 1 /* disable falling back to fork() from system_forkfd() */
#include <sys/types.h>
#if defined(__OpenBSD__) || defined(__NetBSD__)
# include <sys/param.h>
@ -96,6 +101,16 @@ static int system_has_forkfd(void);
static int system_forkfd(int flags, pid_t *ppid, int *system);
static int system_forkfd_wait(int ffd, struct forkfd_info *info, int ffdwoptions, struct rusage *rusage);
static int disable_fork_fallback(void)
{
#ifdef FORKFD_DISABLE_FORK_FALLBACK
/* if there's no system forkfd, we have to use the fallback */
return system_has_forkfd();
#else
return false;
#endif
}
#define CHILDREN_IN_SMALL_ARRAY 16
#define CHILDREN_IN_BIG_ARRAY 256
#define sizeofarray(array) (sizeof(array)/sizeof(array[0]))
@ -629,9 +644,12 @@ int forkfd(int flags, pid_t *ppid)
int efd;
#endif
if (disable_fork_fallback())
flags &= ~FFD_USE_FORK;
if ((flags & FFD_USE_FORK) == 0) {
fd = system_forkfd(flags, ppid, &ret);
if (ret)
if (ret || disable_fork_fallback())
return fd;
}
@ -815,8 +833,12 @@ int forkfd_wait4(int ffd, struct forkfd_info *info, int options, struct rusage *
struct pipe_payload payload;
int ret;
if (system_has_forkfd())
return system_forkfd_wait(ffd, info, options, rusage);
if (system_has_forkfd()) {
/* if this is one of our pipes, not a procdesc/pidfd, we'll get an EBADF */
ret = system_forkfd_wait(ffd, info, options, rusage);
if (disable_fork_fallback() || ret != -1 || errno != EBADF)
return ret;
}
ret = read(ffd, &payload, sizeof(payload));
if (ret == -1)

View File

@ -1,7 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Copyright (C) 2020 The Qt Company Ltd.
** Copyright (C) 2020 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@ -40,6 +40,12 @@
#include <QtCore/QDebug>
#include <QtCore/QMetaType>
#include <QtNetwork/QHostInfo>
#include <qplatformdefs.h>
#ifdef Q_OS_UNIX
# include <private/qcore_unix_p.h>
#endif
#include <stdlib.h>
typedef void (QProcess::*QProcessFinishedSignal1)(int);
@ -59,6 +65,7 @@ private slots:
void getSetCheck();
void constructing();
void simpleStart();
void setupChildProcess();
void startWithOpen();
void startWithOldOpen();
void execute();
@ -277,6 +284,51 @@ void tst_QProcess::simpleStart()
QCOMPARE(qvariant_cast<QProcess::ProcessState>(spy.at(2).at(0)), QProcess::NotRunning);
}
void tst_QProcess::setupChildProcess()
{
/* This test exists because in Qt 5.15, the Unix version of QProcess has
* some code that depends on whether it's an actual QProcess or a
* derived class */
static const char setupChildMessage[] = "Called from setupChildProcess()";
class DerivedProcessClass : public QProcess {
public:
int fd;
DerivedProcessClass(int fd) : fd(fd)
{
}
protected:
void setupChildProcess() override
{
QT_WRITE(fd, setupChildMessage, sizeof(setupChildMessage) - 1);
QT_CLOSE(fd);
}
};
int pipes[2] = { -1 , -1 };
#ifdef Q_OS_UNIX
QVERIFY(qt_safe_pipe(pipes) == 0);
#endif
DerivedProcessClass process(pipes[1]);
process.start("testProcessNormal/testProcessNormal");
if (process.state() != QProcess::Starting)
QCOMPARE(process.state(), QProcess::Running);
QVERIFY2(process.waitForStarted(5000), qPrintable(process.errorString()));
#ifdef Q_OS_UNIX
char buf[sizeof setupChildMessage] = {};
qt_safe_close(pipes[1]);
QCOMPARE(qt_safe_read(pipes[0], buf, sizeof(buf)), qint64(sizeof(setupChildMessage) - 1));
QCOMPARE(buf, setupChildMessage);
qt_safe_close(pipes[0]);
#endif
QVERIFY2(process.waitForFinished(5000), qPrintable(process.errorString()));
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
}
void tst_QProcess::startWithOpen()
{
QProcess p;