QProcess::startDetached/Unix: report which function failed

Like QProcess::start().

Change-Id: Ic90d8429a0eb4837971dfffd1664ef1293a6523d
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
(cherry picked from commit 5d977b0fd2fa4611571e50378647c09febd49141)
This commit is contained in:
Thiago Macieira 2021-02-18 12:14:11 -08:00
parent 4ffc03d9fd
commit 0ea39a7c42
2 changed files with 46 additions and 30 deletions

View File

@ -168,6 +168,12 @@ struct AutoPipe
int operator[](int idx) const { return pipe[idx]; } int operator[](int idx) const { return pipe[idx]; }
}; };
struct ChildError
{
qint64 code;
char function[8];
};
struct QProcessPoller struct QProcessPoller
{ {
QProcessPoller(const QProcessPrivate &proc); QProcessPoller(const QProcessPrivate &proc);
@ -552,12 +558,6 @@ void QProcessPrivate::startProcess()
::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK);
} }
struct ChildError
{
int code;
char function[8];
};
void QProcessPrivate::execChild(const char *workingDir, char **argv, char **envp) void QProcessPrivate::execChild(const char *workingDir, char **argv, char **envp)
{ {
::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
@ -902,7 +902,9 @@ bool QProcessPrivate::startDetached(qint64 *pid)
{ {
QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory); QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory);
// To catch the startup of the child and communicate its pid static_assert(PIPE_BUF >= sizeof(ChildError));
ChildError childStatus = { 0, {} };
AutoPipe startedPipe, pidPipe; AutoPipe startedPipe, pidPipe;
if (!startedPipe || !pidPipe) { if (!startedPipe || !pidPipe) {
setErrorAndEmit(QProcess::FailedToStart, QLatin1String("pipe: ") + qt_error_string(errno)); setErrorAndEmit(QProcess::FailedToStart, QLatin1String("pipe: ") + qt_error_string(errno));
@ -925,15 +927,20 @@ 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]);
pid_t doubleForkPid = 0; auto reportFailed = [&](const char *function) {
if (!encodedWorkingDirectory.isEmpty()) childStatus.code = errno;
doubleForkPid = QT_CHDIR(encodedWorkingDirectory.constData()); strcpy(childStatus.function, function);
qt_safe_write(startedPipe[1], &childStatus, sizeof(childStatus));
::_exit(1);
};
if (doubleForkPid == 0) if (!encodedWorkingDirectory.isEmpty()) {
doubleForkPid = fork(); if (QT_CHDIR(encodedWorkingDirectory.constData()) < 0)
reportFailed("chdir: ");
}
pid_t doubleForkPid = fork();
if (doubleForkPid == 0) { if (doubleForkPid == 0) {
qt_safe_close(pidPipe[1]);
// Render channels configuration. // Render channels configuration.
commitChannels(); commitChannels();
@ -964,19 +971,13 @@ bool QProcessPrivate::startDetached(qint64 *pid)
else else
qt_safe_execv(argv[0], argv); qt_safe_execv(argv[0], argv);
// '\1' means execv failed reportFailed("execv: ");
char c = '\1';
qt_safe_write(startedPipe[1], &c, 1);
qt_safe_close(startedPipe[1]);
::_exit(1);
} else if (doubleForkPid == -1) { } else if (doubleForkPid == -1) {
// '\2' means internal error reportFailed("fork: ");
char c = '\2';
qt_safe_write(startedPipe[1], &c, 1);
} }
qt_safe_close(startedPipe[1]); // success
qt_safe_write(pidPipe[1], (const char *)&doubleForkPid, sizeof(pid_t)); qt_safe_write(pidPipe[1], &doubleForkPid, sizeof(pid_t));
::_exit(1); ::_exit(1);
} }
@ -990,24 +991,35 @@ bool QProcessPrivate::startDetached(qint64 *pid)
return false; return false;
} }
// close the writing ends of the pipes so we can properly get EOFs
qt_safe_close(pidPipe[1]);
qt_safe_close(startedPipe[1]); qt_safe_close(startedPipe[1]);
startedPipe[1] = -1; pidPipe[1] = startedPipe[1] = -1;
char reply = '\0'; // This read() will block until we're cleared to proceed. If it returns 0
int startResult = qt_safe_read(startedPipe[0], &reply, 1); // (EOF), it means the direct child has exited and the grandchild
// successfully execve()'d the target process. If it returns any positive
// result, it means one of the two children wrote an error result. Negative
// values should not happen.
ssize_t startResult = qt_safe_read(startedPipe[0], &childStatus, sizeof(childStatus));
// reap the intermediate child
int result; int result;
qt_safe_waitpid(childPid, &result, 0); qt_safe_waitpid(childPid, &result, 0);
bool success = (startResult != -1 && reply == '\0'); bool success = (startResult == 0); // nothing written -> no error
if (success && pid) { if (success && pid) {
pid_t actualPid; pid_t actualPid;
if (qt_safe_read(pidPipe[0], &actualPid, sizeof(pid_t)) != sizeof(pid_t)) if (qt_safe_read(pidPipe[0], &actualPid, sizeof(pid_t)) != sizeof(pid_t))
actualPid = 0; actualPid = 0; // this shouldn't happen!
*pid = actualPid; *pid = actualPid;
} else if (!success) { } else if (!success) {
if (pid) if (pid)
*pid = -1; *pid = -1;
setErrorAndEmit(QProcess::FailedToStart); QString msg;
if (startResult == sizeof(childStatus))
msg = QLatin1String(childStatus.function) + qt_error_string(childStatus.code);
setErrorAndEmit(QProcess::FailedToStart, msg);
} }
return success; return success;
} }

View File

@ -2281,6 +2281,10 @@ void tst_QProcess::detachedSetNonExistentWorkingDirectory()
QCOMPARE(pid, -1); QCOMPARE(pid, -1);
QCOMPARE(process.error(), QProcess::FailedToStart); QCOMPARE(process.error(), QProcess::FailedToStart);
QVERIFY(process.errorString() != "Unknown error"); QVERIFY(process.errorString() != "Unknown error");
#ifdef Q_OS_UNIX
QVERIFY2(process.errorString().startsWith("chdir:"), process.errorString().toLocal8Bit());
#endif
} }
void tst_QProcess::startFinishStartFinish() void tst_QProcess::startFinishStartFinish()