QProcess/Unix: add failChildProcessModifier()
QProcess detects other types of failures from inside the modifier as successful starts, because the childStartedPipe gets closed without an error condition getting written. The new method allows a reporting as a proper failure-to-start. Added tests for both cases. [ChangeLog][QtCore][QProcess] Added failChildProcessModifier(). Change-Id: Icfe44ecf285a480fafe4fffd174da2b10306d3c2 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
abd2ffc149
commit
90bc0ad41f
@ -1646,8 +1646,10 @@ std::function<void(void)> QProcess::childProcessModifier() const
|
|||||||
|
|
||||||
\snippet code/src_corelib_io_qprocess.cpp 4
|
\snippet code/src_corelib_io_qprocess.cpp 4
|
||||||
|
|
||||||
If the modifier function needs to exit the process, remember to use
|
If the modifier function experiences a failure condition, it can use
|
||||||
\c{_exit()}, not \c{exit()}.
|
failChildProcessModifier() to report the situation to the QProcess caller.
|
||||||
|
Alternatively, it may use other methods of stopping the process, like
|
||||||
|
\c{_exit()}, or \c{abort()}.
|
||||||
|
|
||||||
Certain properties of the child process, such as closing all extraneous
|
Certain properties of the child process, such as closing all extraneous
|
||||||
file descriptors or disconnecting from the controlling TTY, can be more
|
file descriptors or disconnecting from the controlling TTY, can be more
|
||||||
@ -1674,7 +1676,7 @@ std::function<void(void)> QProcess::childProcessModifier() const
|
|||||||
only make use of low-level system calls, such as \c{read()},
|
only make use of low-level system calls, such as \c{read()},
|
||||||
\c{write()}, \c{setsid()}, \c{nice()}, and similar.
|
\c{write()}, \c{setsid()}, \c{nice()}, and similar.
|
||||||
|
|
||||||
\sa childProcessModifier(), setUnixProcessParameters()
|
\sa childProcessModifier(), failChildProcessModifier(), setUnixProcessParameters()
|
||||||
*/
|
*/
|
||||||
void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier)
|
void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier)
|
||||||
{
|
{
|
||||||
@ -1684,6 +1686,46 @@ void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier
|
|||||||
d->unixExtras->childProcessModifier = modifier;
|
d->unixExtras->childProcessModifier = modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn void QProcess::failChildProcessModifier(const char *description, int error) noexcept
|
||||||
|
\since 6.7
|
||||||
|
|
||||||
|
This functions can be used inside the modifier set with
|
||||||
|
setChildProcessModifier() to indicate an error condition was encountered.
|
||||||
|
When the modifier calls these functions, QProcess will emit errorOccurred()
|
||||||
|
with code QProcess::FailedToStart in the parent process. The \a description
|
||||||
|
can be used to include some information in errorString() to help diagnose
|
||||||
|
the problem, usually the name of the call that failed, similar to the C
|
||||||
|
Library function \c{perror()}. Additionally, the \a error parameter can be
|
||||||
|
an \c{<errno.h>} error code whose text form will also be included.
|
||||||
|
|
||||||
|
For example, a child modifier could prepare an extra file descriptor for
|
||||||
|
the child process this way:
|
||||||
|
|
||||||
|
\code
|
||||||
|
process.setChildProcessModifier([fd, &process]() {
|
||||||
|
if (dup2(fd, TargetFileDescriptor) < 0)
|
||||||
|
process.failChildProcessModifier(errno, "aux comm channel");
|
||||||
|
});
|
||||||
|
process.start();
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
Where \c{fd} is a file descriptor currently open in the parent process. If
|
||||||
|
the \c{dup2()} system call resulted in an \c EBADF condition, the process
|
||||||
|
errorString() could be "Child process modifier reported error: aux comm
|
||||||
|
channel: Bad file descriptor".
|
||||||
|
|
||||||
|
This function does not return to the caller. Using it anywhere except in
|
||||||
|
the child modifier and with the correct QProcess object is undefined
|
||||||
|
behavior.
|
||||||
|
|
||||||
|
\note The implementation imposes a length limit to the \a description
|
||||||
|
parameter to about 500 characters. This does not include the text from the
|
||||||
|
\a error code.
|
||||||
|
|
||||||
|
\sa setChildProcessModifier(), setUnixProcessParameters()
|
||||||
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\since 6.6
|
\since 6.6
|
||||||
Returns the \l UnixProcessParameters object describing extra flags and
|
Returns the \l UnixProcessParameters object describing extra flags and
|
||||||
|
@ -174,6 +174,7 @@ public:
|
|||||||
#if defined(Q_OS_UNIX) || defined(Q_QDOC)
|
#if defined(Q_OS_UNIX) || defined(Q_QDOC)
|
||||||
std::function<void(void)> childProcessModifier() const;
|
std::function<void(void)> childProcessModifier() const;
|
||||||
void setChildProcessModifier(const std::function<void(void)> &modifier);
|
void setChildProcessModifier(const std::function<void(void)> &modifier);
|
||||||
|
Q_NORETURN void failChildProcessModifier(const char *description, int error = 0) noexcept;
|
||||||
|
|
||||||
enum UnixProcessFlag : quint32 {
|
enum UnixProcessFlag : quint32 {
|
||||||
ResetSignalHandlers = 0x0001, // like POSIX_SPAWN_SETSIGDEF
|
ResetSignalHandlers = 0x0001, // like POSIX_SPAWN_SETSIGDEF
|
||||||
|
@ -304,7 +304,7 @@ public:
|
|||||||
void startProcess();
|
void startProcess();
|
||||||
#if defined(Q_OS_UNIX)
|
#if defined(Q_OS_UNIX)
|
||||||
void commitChannels() const;
|
void commitChannels() const;
|
||||||
void execChild(int workingDirectory, char **argv, char **envp) const noexcept;
|
Q_NORETURN void execChild(int workingDirectory, char **argv, char **envp) const noexcept;
|
||||||
#endif
|
#endif
|
||||||
bool processStarted(QString *errorMessage = nullptr);
|
bool processStarted(QString *errorMessage = nullptr);
|
||||||
void processFinished();
|
void processFinished();
|
||||||
|
@ -184,8 +184,12 @@ struct AutoPipe
|
|||||||
struct ChildError
|
struct ChildError
|
||||||
{
|
{
|
||||||
int code;
|
int code;
|
||||||
char function[12];
|
char function[_POSIX_PIPE_BUF - sizeof(code)];
|
||||||
};
|
};
|
||||||
|
static_assert(std::is_trivial_v<ChildError>);
|
||||||
|
#ifdef PIPE_BUF
|
||||||
|
static_assert(PIPE_BUF >= sizeof(ChildError)); // PIPE_BUF may be bigger
|
||||||
|
#endif
|
||||||
|
|
||||||
// Used for argv and envp arguments to execve()
|
// Used for argv and envp arguments to execve()
|
||||||
struct CharPointerList
|
struct CharPointerList
|
||||||
@ -645,6 +649,42 @@ static constexpr int FakeErrnoForThrow =
|
|||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
|
static QString startFailureErrorMessage(ChildError &err, [[maybe_unused]] ssize_t bytesRead)
|
||||||
|
{
|
||||||
|
// ChildError is less than the POSIX pipe buffer atomic size, so the read
|
||||||
|
// must not have been truncated
|
||||||
|
Q_ASSERT(bytesRead == sizeof(err));
|
||||||
|
|
||||||
|
qsizetype len = qstrnlen(err.function, sizeof(err.function));
|
||||||
|
QString complement = QString::fromUtf8(err.function, len);
|
||||||
|
if (err.code == FakeErrnoForThrow)
|
||||||
|
return QProcess::tr("Child process modifier threw an exception");
|
||||||
|
if (err.code == 0)
|
||||||
|
return QProcess::tr("Child process modifier reported error: %1")
|
||||||
|
.arg(std::move(complement));
|
||||||
|
if (err.code < 0)
|
||||||
|
return QProcess::tr("Child process modifier reported error: %1: %2")
|
||||||
|
.arg(std::move(complement), qt_error_string(-err.code));
|
||||||
|
return QProcess::tr("Child process set up failed: %1: %2")
|
||||||
|
.arg(std::move(complement), qt_error_string(err.code));
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_NORETURN void
|
||||||
|
failChildProcess(const QProcessPrivate *d, const char *description, int code) noexcept
|
||||||
|
{
|
||||||
|
ChildError error = {};
|
||||||
|
error.code = code;
|
||||||
|
strncpy(error.function, description, sizeof(error.function));
|
||||||
|
qt_safe_write(d->childStartedPipe[1], &error, sizeof(error));
|
||||||
|
_exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QProcess::failChildProcessModifier(const char *description, int error) noexcept
|
||||||
|
{
|
||||||
|
// We signal user errors with negative errnos
|
||||||
|
failChildProcess(d_func(), description, -error);
|
||||||
|
}
|
||||||
|
|
||||||
// See IMPORTANT notice below
|
// See IMPORTANT notice below
|
||||||
static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms)
|
static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms)
|
||||||
{
|
{
|
||||||
@ -704,22 +744,21 @@ static const char *callChildProcessModifier(const QProcessPrivate::UnixExtras *u
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function doesn't return if the execution succeeds
|
Q_NORETURN static void
|
||||||
static const char *doExecChild(char **argv, char **envp, int workingDirFd,
|
doExecChild(char **argv, char **envp, int workingDirFd, const QProcessPrivate *d) noexcept
|
||||||
const QProcessPrivate::UnixExtras *unixExtras) noexcept
|
|
||||||
{
|
{
|
||||||
// enter the working directory
|
// enter the working directory
|
||||||
if (workingDirFd != -1 && fchdir(workingDirFd) == -1)
|
if (workingDirFd != -1 && fchdir(workingDirFd) == -1)
|
||||||
return "fchdir";
|
failChildProcess(d, "fchdir", errno);
|
||||||
|
|
||||||
if (unixExtras) {
|
if (d->unixExtras) {
|
||||||
// FIRST we call the user modifier function, before we dropping
|
// FIRST we call the user modifier function, before we dropping
|
||||||
// privileges or closing non-standard file descriptors
|
// privileges or closing non-standard file descriptors
|
||||||
if (const char *what = callChildProcessModifier(unixExtras))
|
if (const char *what = callChildProcessModifier(d->unixExtras.get()))
|
||||||
return what;
|
failChildProcess(d, what, FakeErrnoForThrow);
|
||||||
|
|
||||||
// then we apply our other user-provided parameters
|
// then we apply our other user-provided parameters
|
||||||
applyProcessParameters(unixExtras->processParameters);
|
applyProcessParameters(d->unixExtras->processParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the process
|
// execute the process
|
||||||
@ -727,10 +766,9 @@ static const char *doExecChild(char **argv, char **envp, int workingDirFd,
|
|||||||
qt_safe_execv(argv[0], argv);
|
qt_safe_execv(argv[0], argv);
|
||||||
else
|
else
|
||||||
qt_safe_execve(argv[0], argv, envp);
|
qt_safe_execve(argv[0], argv, envp);
|
||||||
return "execve";
|
failChildProcess(d, "execve", errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// IMPORTANT:
|
// IMPORTANT:
|
||||||
//
|
//
|
||||||
// This function is called in a vfork() context on some OSes (notably, Linux
|
// This function is called in a vfork() context on some OSes (notably, Linux
|
||||||
@ -740,22 +778,13 @@ void QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const
|
|||||||
{
|
{
|
||||||
QtVforkSafe::ignore_sigpipe(); // reset the signal that we ignored
|
QtVforkSafe::ignore_sigpipe(); // reset the signal that we ignored
|
||||||
|
|
||||||
ChildError error = { 0, {} }; // force zeroing of function[8]
|
|
||||||
|
|
||||||
// Render channels configuration.
|
// Render channels configuration.
|
||||||
commitChannels();
|
commitChannels();
|
||||||
|
|
||||||
// make sure this fd is closed if execv() succeeds
|
// make sure this fd is closed if execv() succeeds
|
||||||
qt_safe_close(childStartedPipe[0]);
|
qt_safe_close(childStartedPipe[0]);
|
||||||
|
|
||||||
const char *what = doExecChild(argv, envp, workingDir, unixExtras.get());
|
doExecChild(argv, envp, workingDir, this);
|
||||||
strcpy(error.function, what);
|
|
||||||
|
|
||||||
// notify failure
|
|
||||||
// don't use strerror or any other routines that may allocate memory, since
|
|
||||||
// some buggy libc versions can deadlock on locked mutexes.
|
|
||||||
error.code = errno;
|
|
||||||
qt_safe_write(childStartedPipe[1], &error, sizeof(error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QProcessPrivate::processStarted(QString *errorMessage)
|
bool QProcessPrivate::processStarted(QString *errorMessage)
|
||||||
@ -792,12 +821,8 @@ bool QProcessPrivate::processStarted(QString *errorMessage)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// did we read an error message?
|
// did we read an error message?
|
||||||
if (errorMessage) {
|
if (errorMessage)
|
||||||
if (buf.code == FakeErrnoForThrow)
|
*errorMessage = startFailureErrorMessage(buf, ret);
|
||||||
*errorMessage = QProcess::tr("childProcessModifier() function threw an exception");
|
|
||||||
else
|
|
||||||
*errorMessage = QLatin1StringView(buf.function) + ": "_L1 + qt_error_string(buf.code);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1107,15 +1132,8 @@ void QProcessPrivate::waitForDeadChild()
|
|||||||
|
|
||||||
bool QProcessPrivate::startDetached(qint64 *pid)
|
bool QProcessPrivate::startDetached(qint64 *pid)
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef PIPE_BUF
|
|
||||||
static_assert(PIPE_BUF >= sizeof(ChildError));
|
|
||||||
#else
|
|
||||||
static_assert(_POSIX_PIPE_BUF >= sizeof(ChildError));
|
|
||||||
#endif
|
|
||||||
ChildError childStatus = { 0, {} };
|
|
||||||
|
|
||||||
AutoPipe startedPipe, pidPipe;
|
AutoPipe startedPipe, pidPipe;
|
||||||
|
childStartedPipe[1] = startedPipe[1];
|
||||||
if (!startedPipe || !pidPipe) {
|
if (!startedPipe || !pidPipe) {
|
||||||
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
|
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
|
||||||
return false;
|
return false;
|
||||||
@ -1155,22 +1173,14 @@ bool QProcessPrivate::startDetached(qint64 *pid)
|
|||||||
qt_safe_close(startedPipe[0]);
|
qt_safe_close(startedPipe[0]);
|
||||||
qt_safe_close(pidPipe[0]);
|
qt_safe_close(pidPipe[0]);
|
||||||
|
|
||||||
auto reportFailed = [&](const char *function) {
|
|
||||||
childStatus.code = errno;
|
|
||||||
strcpy(childStatus.function, function);
|
|
||||||
qt_safe_write(startedPipe[1], &childStatus, sizeof(childStatus));
|
|
||||||
::_exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
pid_t doubleForkPid = doFork();
|
pid_t doubleForkPid = doFork();
|
||||||
if (doubleForkPid == 0) {
|
if (doubleForkPid == 0) {
|
||||||
// Render channels configuration.
|
// Render channels configuration.
|
||||||
commitChannels();
|
commitChannels();
|
||||||
|
|
||||||
reportFailed(doExecChild(argv.pointers.get(), envp.pointers.get(), workingDirFd,
|
doExecChild(argv.pointers.get(), envp.pointers.get(), workingDirFd, this);
|
||||||
unixExtras.get()));
|
|
||||||
} else if (doubleForkPid == -1) {
|
} else if (doubleForkPid == -1) {
|
||||||
reportFailed("fork: ");
|
failChildProcess(this, "fork", errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
// success
|
// success
|
||||||
@ -1198,6 +1208,7 @@ bool QProcessPrivate::startDetached(qint64 *pid)
|
|||||||
// successfully execve()'d the target process. If it returns any positive
|
// successfully execve()'d the target process. If it returns any positive
|
||||||
// result, it means one of the two children wrote an error result. Negative
|
// result, it means one of the two children wrote an error result. Negative
|
||||||
// values should not happen.
|
// values should not happen.
|
||||||
|
ChildError childStatus;
|
||||||
ssize_t startResult = qt_safe_read(startedPipe[0], &childStatus, sizeof(childStatus));
|
ssize_t startResult = qt_safe_read(startedPipe[0], &childStatus, sizeof(childStatus));
|
||||||
|
|
||||||
// reap the intermediate child
|
// reap the intermediate child
|
||||||
@ -1213,10 +1224,8 @@ bool QProcessPrivate::startDetached(qint64 *pid)
|
|||||||
} else if (!success) {
|
} else if (!success) {
|
||||||
if (pid)
|
if (pid)
|
||||||
*pid = -1;
|
*pid = -1;
|
||||||
QString msg;
|
setErrorAndEmit(QProcess::FailedToStart,
|
||||||
if (startResult == sizeof(childStatus))
|
startFailureErrorMessage(childStatus, startResult));
|
||||||
msg = QLatin1StringView(childStatus.function) + qt_error_string(childStatus.code);
|
|
||||||
setErrorAndEmit(QProcess::FailedToStart, msg);
|
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "crasher.h"
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
typedef void (QProcess::*QProcessErrorSignal)(QProcess::ProcessError);
|
typedef void (QProcess::*QProcessErrorSignal)(QProcess::ProcessError);
|
||||||
|
|
||||||
class tst_QProcess : public QObject
|
class tst_QProcess : public QObject
|
||||||
@ -114,7 +118,11 @@ private slots:
|
|||||||
#if defined(Q_OS_UNIX)
|
#if defined(Q_OS_UNIX)
|
||||||
void setChildProcessModifier_data();
|
void setChildProcessModifier_data();
|
||||||
void setChildProcessModifier();
|
void setChildProcessModifier();
|
||||||
|
void failChildProcessModifier_data() { setChildProcessModifier_data(); }
|
||||||
|
void failChildProcessModifier();
|
||||||
void throwInChildProcessModifier();
|
void throwInChildProcessModifier();
|
||||||
|
void terminateInChildProcessModifier_data();
|
||||||
|
void terminateInChildProcessModifier();
|
||||||
void unixProcessParameters_data();
|
void unixProcessParameters_data();
|
||||||
void unixProcessParameters();
|
void unixProcessParameters();
|
||||||
void unixProcessParametersAndChildModifier();
|
void unixProcessParametersAndChildModifier();
|
||||||
@ -1451,6 +1459,27 @@ void tst_QProcess::createProcessArgumentsModifier()
|
|||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
|
static constexpr int sigs[] = { SIGABRT, SIGILL, SIGSEGV };
|
||||||
|
struct DisableCrashLogger
|
||||||
|
{
|
||||||
|
// disable core dumps too
|
||||||
|
tst_QProcessCrash::NoCoreDumps disableCoreDumps {};
|
||||||
|
std::array<struct sigaction, std::size(sigs)> oldhandlers;
|
||||||
|
DisableCrashLogger()
|
||||||
|
{
|
||||||
|
struct sigaction def = {};
|
||||||
|
def.sa_handler = SIG_DFL;
|
||||||
|
for (uint i = 0; i < std::size(sigs); ++i)
|
||||||
|
sigaction(sigs[i], &def, &oldhandlers[i]);
|
||||||
|
}
|
||||||
|
~DisableCrashLogger()
|
||||||
|
{
|
||||||
|
// restore them
|
||||||
|
for (uint i = 0; i < std::size(sigs); ++i)
|
||||||
|
sigaction(sigs[i], &oldhandlers[i], nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr char messageFromChildProcess[] = "Message from the child process";
|
static constexpr char messageFromChildProcess[] = "Message from the child process";
|
||||||
static_assert(std::char_traits<char>::length(messageFromChildProcess) <= PIPE_BUF);
|
static_assert(std::char_traits<char>::length(messageFromChildProcess) <= PIPE_BUF);
|
||||||
static void childProcessModifier(int fd)
|
static void childProcessModifier(int fd)
|
||||||
@ -1496,6 +1525,36 @@ void tst_QProcess::setChildProcessModifier()
|
|||||||
qt_safe_close(pipes[0]);
|
qt_safe_close(pipes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QProcess::failChildProcessModifier()
|
||||||
|
{
|
||||||
|
static const char failureMsg[] =
|
||||||
|
"Some error message from the child process would go here if this were a "
|
||||||
|
"real application";
|
||||||
|
static_assert(sizeof(failureMsg) < _POSIX_PIPE_BUF / 2,
|
||||||
|
"Implementation detail: the length of the message is limited");
|
||||||
|
|
||||||
|
QFETCH(bool, detached);
|
||||||
|
QProcess process;
|
||||||
|
process.setChildProcessModifier([&process]() {
|
||||||
|
process.failChildProcessModifier(failureMsg, EPERM);
|
||||||
|
});
|
||||||
|
process.setProgram("testProcessNormal/testProcessNormal");
|
||||||
|
|
||||||
|
if (detached) {
|
||||||
|
qint64 pid;
|
||||||
|
QVERIFY(!process.startDetached(&pid));
|
||||||
|
QCOMPARE(pid, -1);
|
||||||
|
} else {
|
||||||
|
process.start("testProcessNormal/testProcessNormal");
|
||||||
|
QVERIFY(!process.waitForStarted(5000));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString errMsg = process.errorString();
|
||||||
|
QVERIFY2(errMsg.startsWith("Child process modifier reported error: "_L1 + failureMsg),
|
||||||
|
qPrintable(errMsg));
|
||||||
|
QVERIFY2(errMsg.endsWith(strerror(EPERM)), qPrintable(errMsg));
|
||||||
|
}
|
||||||
|
|
||||||
void tst_QProcess::throwInChildProcessModifier()
|
void tst_QProcess::throwInChildProcessModifier()
|
||||||
{
|
{
|
||||||
#ifndef __cpp_exceptions
|
#ifndef __cpp_exceptions
|
||||||
@ -1511,7 +1570,7 @@ void tst_QProcess::throwInChildProcessModifier()
|
|||||||
QVERIFY(!process.waitForStarted(5000));
|
QVERIFY(!process.waitForStarted(5000));
|
||||||
QCOMPARE(process.state(), QProcess::NotRunning);
|
QCOMPARE(process.state(), QProcess::NotRunning);
|
||||||
QCOMPARE(process.error(), QProcess::FailedToStart);
|
QCOMPARE(process.error(), QProcess::FailedToStart);
|
||||||
QVERIFY2(process.errorString().contains("childProcessModifier"),
|
QVERIFY2(process.errorString().contains("Child process modifier threw an exception"),
|
||||||
qPrintable(process.errorString()));
|
qPrintable(process.errorString()));
|
||||||
|
|
||||||
// try again, to ensure QProcess internal state wasn't corrupted
|
// try again, to ensure QProcess internal state wasn't corrupted
|
||||||
@ -1519,11 +1578,59 @@ void tst_QProcess::throwInChildProcessModifier()
|
|||||||
QVERIFY(!process.waitForStarted(5000));
|
QVERIFY(!process.waitForStarted(5000));
|
||||||
QCOMPARE(process.state(), QProcess::NotRunning);
|
QCOMPARE(process.state(), QProcess::NotRunning);
|
||||||
QCOMPARE(process.error(), QProcess::FailedToStart);
|
QCOMPARE(process.error(), QProcess::FailedToStart);
|
||||||
QVERIFY2(process.errorString().contains("childProcessModifier"),
|
QVERIFY2(process.errorString().contains("Child process modifier threw an exception"),
|
||||||
qPrintable(process.errorString()));
|
qPrintable(process.errorString()));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QProcess::terminateInChildProcessModifier_data()
|
||||||
|
{
|
||||||
|
using F = std::function<void(void)>;
|
||||||
|
QTest::addColumn<F>("function");
|
||||||
|
QTest::addColumn<QProcess::ExitStatus>("exitStatus");
|
||||||
|
QTest::addColumn<bool>("stderrIsEmpty");
|
||||||
|
|
||||||
|
QTest::newRow("_exit") << F([]() { _exit(0); }) << QProcess::NormalExit << true;
|
||||||
|
QTest::newRow("abort") << F(std::abort) << QProcess::CrashExit << true;
|
||||||
|
QTest::newRow("sigkill") << F([]() { raise(SIGKILL); }) << QProcess::CrashExit << true;
|
||||||
|
QTest::newRow("terminate") << F(std::terminate) << QProcess::CrashExit
|
||||||
|
<< (std::get_terminate() == std::abort);
|
||||||
|
QTest::newRow("crash") << F([]() { tst_QProcessCrash::crash(); }) << QProcess::CrashExit << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QProcess::terminateInChildProcessModifier()
|
||||||
|
{
|
||||||
|
QFETCH(std::function<void(void)>, function);
|
||||||
|
QFETCH(QProcess::ExitStatus, exitStatus);
|
||||||
|
QFETCH(bool, stderrIsEmpty);
|
||||||
|
|
||||||
|
// temporarily disable QTest's crash logger
|
||||||
|
DisableCrashLogger disableCrashLogging;
|
||||||
|
|
||||||
|
QProcess process;
|
||||||
|
process.setChildProcessModifier(function);
|
||||||
|
process.setProgram("testProcessNormal/testProcessNormal");
|
||||||
|
|
||||||
|
// temporarily disable QTest's crash logger while starting the child process
|
||||||
|
{
|
||||||
|
DisableCrashLogger d;
|
||||||
|
process.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY2(process.waitForStarted(5000), qPrintable(process.errorString()));
|
||||||
|
QVERIFY2(process.waitForFinished(5000), qPrintable(process.errorString()));
|
||||||
|
QCOMPARE(process.exitStatus(), exitStatus);
|
||||||
|
|
||||||
|
// some environments print extra stuff to stderr when we crash
|
||||||
|
#ifndef Q_OS_QNX
|
||||||
|
if (!QTestPrivate::isRunningArmOnX86()) {
|
||||||
|
QByteArray standardError = process.readAllStandardError();
|
||||||
|
QVERIFY2(standardError.isEmpty() == stderrIsEmpty,
|
||||||
|
"stderr was: " + standardError);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void tst_QProcess::unixProcessParameters_data()
|
void tst_QProcess::unixProcessParameters_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QProcess::UnixProcessParameters>("params");
|
QTest::addColumn<QProcess::UnixProcessParameters>("params");
|
||||||
@ -1644,16 +1751,12 @@ void tst_QProcess::unixProcessParametersAndChildModifier()
|
|||||||
void tst_QProcess::unixProcessParametersOtherFileDescriptors()
|
void tst_QProcess::unixProcessParametersOtherFileDescriptors()
|
||||||
{
|
{
|
||||||
constexpr int TargetFileDescriptor = 3;
|
constexpr int TargetFileDescriptor = 3;
|
||||||
int pipes[2];
|
|
||||||
int fd1 = open("/dev/null", O_RDONLY);
|
int fd1 = open("/dev/null", O_RDONLY);
|
||||||
int devnull = fcntl(fd1, F_DUPFD, 100); // instead of F_DUPFD_CLOEXEC
|
int devnull = fcntl(fd1, F_DUPFD, 100); // instead of F_DUPFD_CLOEXEC
|
||||||
pipe2(pipes, O_CLOEXEC);
|
|
||||||
close(fd1);
|
close(fd1);
|
||||||
|
|
||||||
auto closeFds = qScopeGuard([&] {
|
auto closeFds = qScopeGuard([&] {
|
||||||
close(devnull);
|
close(devnull);
|
||||||
close(pipes[0]);
|
|
||||||
// we'll close pipe[1] before any QCOMPARE
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QProcess process;
|
QProcess process;
|
||||||
@ -1662,20 +1765,14 @@ void tst_QProcess::unixProcessParametersOtherFileDescriptors()
|
|||||||
| QProcess::UnixProcessFlag::UseVFork;
|
| QProcess::UnixProcessFlag::UseVFork;
|
||||||
params.lowestFileDescriptorToClose = 4;
|
params.lowestFileDescriptorToClose = 4;
|
||||||
process.setUnixProcessParameters(params);
|
process.setUnixProcessParameters(params);
|
||||||
process.setChildProcessModifier([devnull, pipes]() {
|
process.setChildProcessModifier([devnull, &process]() {
|
||||||
if (dup2(devnull, TargetFileDescriptor) == TargetFileDescriptor)
|
if (dup2(devnull, TargetFileDescriptor) != TargetFileDescriptor)
|
||||||
return;
|
process.failChildProcessModifier("dup2", errno);
|
||||||
write(pipes[1], &errno, sizeof(errno));
|
|
||||||
_exit(255);
|
|
||||||
});
|
});
|
||||||
process.setProgram("testUnixProcessParameters/testUnixProcessParameters");
|
process.setProgram("testUnixProcessParameters/testUnixProcessParameters");
|
||||||
process.setArguments({ "file-descriptors2", QString::number(TargetFileDescriptor),
|
process.setArguments({ "file-descriptors2", QString::number(TargetFileDescriptor),
|
||||||
QString::number(devnull) });
|
QString::number(devnull) });
|
||||||
process.start();
|
process.start();
|
||||||
close(pipes[1]);
|
|
||||||
|
|
||||||
if (int duperror; read(pipes[0], &duperror, sizeof(duperror)) == sizeof(duperror))
|
|
||||||
QFAIL(QByteArray("dup2 failed: ") + strerror(duperror));
|
|
||||||
|
|
||||||
QVERIFY2(process.waitForStarted(5000), qPrintable(process.errorString()));
|
QVERIFY2(process.waitForStarted(5000), qPrintable(process.errorString()));
|
||||||
QVERIFY(process.waitForFinished(5000));
|
QVERIFY(process.waitForFinished(5000));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user