From 7ad55ca65f42351e231f31f7a9253ae6eaf1ebb3 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Tue, 30 May 2017 13:53:33 +0200 Subject: Support standard channel redirection in QProcess::startDetached [ChangeLog][QtCore][QProcess] Added support for standard channel redirection using setStandard{Input|Output|Error}File to QProcess::startDetached. Task-number: QTBUG-2058 Task-number: QTBUG-37656 Change-Id: Iafb9bd7899f752d0305e3410ad4dcb7ef598dc79 Reviewed-by: Thiago Macieira --- src/corelib/io/qprocess_unix.cpp | 26 +++++++ src/corelib/io/qprocess_win.cpp | 62 ++++++++++----- .../auto/corelib/io/qprocess/testDetached/main.cpp | 87 ++++++++++++++++++---- tests/auto/corelib/io/qprocess/tst_qprocess.cpp | 36 ++++++++- 4 files changed, 175 insertions(+), 36 deletions(-) diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 251f68c9b4..2b47f3e5b1 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -915,6 +915,17 @@ bool QProcessPrivate::startDetached(qint64 *pid) return false; } + if ((stdinChannel.type == Channel::Redirect && !openChannel(stdinChannel)) + || (stdoutChannel.type == Channel::Redirect && !openChannel(stdoutChannel)) + || (stderrChannel.type == Channel::Redirect && !openChannel(stderrChannel))) { + closeChannel(&stdinChannel); + closeChannel(&stdoutChannel); + closeChannel(&stderrChannel); + qt_safe_close(startedPipe[0]); + qt_safe_close(startedPipe[1]); + return false; + } + pid_t childPid = fork(); if (childPid == 0) { struct sigaction noaction; @@ -931,6 +942,18 @@ bool QProcessPrivate::startDetached(qint64 *pid) if (doubleForkPid == 0) { qt_safe_close(pidPipe[1]); + // copy the stdin socket if asked to (without closing on exec) + if (inputChannelMode != QProcess::ForwardedInputChannel) + qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0); + + // copy the stdout and stderr if asked to + if (processChannelMode != QProcess::ForwardedChannels) { + if (processChannelMode != QProcess::ForwardedOutputChannel) + qt_safe_dup2(stdoutChannel.pipe[1], STDOUT_FILENO, 0); + if (processChannelMode != QProcess::ForwardedErrorChannel) + qt_safe_dup2(stderrChannel.pipe[1], STDERR_FILENO, 0); + } + if (!encodedWorkingDirectory.isEmpty()) { if (QT_CHDIR(encodedWorkingDirectory.constData()) == -1) qWarning("QProcessPrivate::startDetached: failed to chdir to %s", encodedWorkingDirectory.constData()); @@ -992,6 +1015,9 @@ bool QProcessPrivate::startDetached(qint64 *pid) ::_exit(1); } + closeChannel(&stdinChannel); + closeChannel(&stdoutChannel); + closeChannel(&stderrChannel); qt_safe_close(startedPipe[1]); qt_safe_close(pidPipe[1]); diff --git a/src/corelib/io/qprocess_win.cpp b/src/corelib/io/qprocess_win.cpp index fc4d3b225d..0f6a61496d 100644 --- a/src/corelib/io/qprocess_win.cpp +++ b/src/corelib/io/qprocess_win.cpp @@ -462,10 +462,24 @@ bool QProcessPrivate::callCreateProcess(QProcess::CreateProcessArguments *cpargs { if (modifyCreateProcessArgs) modifyCreateProcessArgs(cpargs); - return CreateProcess(cpargs->applicationName, cpargs->arguments, cpargs->processAttributes, - cpargs->threadAttributes, cpargs->inheritHandles, cpargs->flags, - cpargs->environment, cpargs->currentDirectory, cpargs->startupInfo, - cpargs->processInformation); + bool success = CreateProcess(cpargs->applicationName, cpargs->arguments, + cpargs->processAttributes, cpargs->threadAttributes, + cpargs->inheritHandles, cpargs->flags, cpargs->environment, + cpargs->currentDirectory, cpargs->startupInfo, + cpargs->processInformation); + if (stdinChannel.pipe[0] != INVALID_Q_PIPE) { + CloseHandle(stdinChannel.pipe[0]); + stdinChannel.pipe[0] = INVALID_Q_PIPE; + } + if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) { + CloseHandle(stdoutChannel.pipe[1]); + stdoutChannel.pipe[1] = INVALID_Q_PIPE; + } + if (stderrChannel.pipe[1] != INVALID_Q_PIPE) { + CloseHandle(stderrChannel.pipe[1]); + stderrChannel.pipe[1] = INVALID_Q_PIPE; + } + return success; } void QProcessPrivate::startProcess() @@ -535,19 +549,6 @@ void QProcessPrivate::startProcess() errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string()); } - if (stdinChannel.pipe[0] != INVALID_Q_PIPE) { - CloseHandle(stdinChannel.pipe[0]); - stdinChannel.pipe[0] = INVALID_Q_PIPE; - } - if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) { - CloseHandle(stdoutChannel.pipe[1]); - stdoutChannel.pipe[1] = INVALID_Q_PIPE; - } - if (stderrChannel.pipe[1] != INVALID_Q_PIPE) { - CloseHandle(stderrChannel.pipe[1]); - stderrChannel.pipe[1] = INVALID_Q_PIPE; - } - if (!success) { cleanup(); setErrorAndEmit(QProcess::FailedToStart, errorString); @@ -874,6 +875,15 @@ bool QProcessPrivate::startDetached(qint64 *pid) { static const DWORD errorElevationRequired = 740; + if ((stdinChannel.type == Channel::Redirect && !openChannel(stdinChannel)) + || (stdoutChannel.type == Channel::Redirect && !openChannel(stdoutChannel)) + || (stderrChannel.type == Channel::Redirect && !openChannel(stderrChannel))) { + closeChannel(&stdinChannel); + closeChannel(&stdoutChannel); + closeChannel(&stderrChannel); + return false; + } + QString args = qt_create_commandline(program, arguments, nativeArguments); bool success = false; PROCESS_INFORMATION pinfo; @@ -890,11 +900,18 @@ bool QProcessPrivate::startDetached(qint64 *pid) STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, + STARTF_USESTDHANDLES, + 0, 0, 0, + stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1] }; + + const bool inheritHandles = stdinChannel.type == Channel::Redirect + || stdoutChannel.type == Channel::Redirect + || stderrChannel.type == Channel::Redirect; QProcess::CreateProcessArguments cpargs = { nullptr, reinterpret_cast(const_cast(args.utf16())), - nullptr, nullptr, false, dwCreationFlags, envPtr, + nullptr, nullptr, inheritHandles, dwCreationFlags, envPtr, workingDirectory.isEmpty() ? nullptr : reinterpret_cast(workingDirectory.utf16()), &startupInfo, &pinfo @@ -909,10 +926,17 @@ bool QProcessPrivate::startDetached(qint64 *pid) } else if (GetLastError() == errorElevationRequired) { if (envPtr) qWarning("QProcess: custom environment will be ignored for detached elevated process."); + if (!stdinChannel.file.isEmpty() || !stdoutChannel.file.isEmpty() + || !stderrChannel.file.isEmpty()) { + qWarning("QProcess: file redirection is unsupported for detached elevated processes."); + } success = startDetachedUacPrompt(program, arguments, nativeArguments, workingDirectory, pid); } + closeChannel(&stdinChannel); + closeChannel(&stdoutChannel); + closeChannel(&stderrChannel); return success; } diff --git a/tests/auto/corelib/io/qprocess/testDetached/main.cpp b/tests/auto/corelib/io/qprocess/testDetached/main.cpp index c970ce5aa0..c10e32d584 100644 --- a/tests/auto/corelib/io/qprocess/testDetached/main.cpp +++ b/tests/auto/corelib/io/qprocess/testDetached/main.cpp @@ -40,23 +40,8 @@ #include #endif -int main(int argc, char **argv) +static void writeStuff(QFile &f) { - QCoreApplication app(argc, argv); - - QStringList args = app.arguments(); - if (args.count() != 2) { - fprintf(stderr, "Usage: testDetached filename.txt\n"); - return 128; - } - - QFile f(args.at(1)); - if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - fprintf(stderr, "Cannot open %s for writing: %s\n", - qPrintable(f.fileName()), qPrintable(f.errorString())); - return 1; - } - f.write(QDir::currentPath().toUtf8()); f.putChar('\n'); #if defined(Q_OS_UNIX) @@ -67,7 +52,77 @@ int main(int argc, char **argv) f.putChar('\n'); f.write(qgetenv("tst_QProcess")); f.putChar('\n'); +} + +struct Args +{ + int exitCode = 0; + QByteArray errorMessage; + QString fileName; + FILE *channel = nullptr; + QByteArray channelName; +}; + +static Args parseArguments(const QStringList &args) +{ + Args result; + if (args.count() < 2) { + result.exitCode = 128; + result.errorMessage = "Usage: testDetached [--out-channel={stdout|stderr}] filename.txt\n"; + return result; + } + for (const QString &arg : args) { + if (arg.startsWith("--")) { + if (!arg.startsWith("--out-channel=")) { + result.exitCode = 2; + result.errorMessage = "Unknown argument " + arg.toLocal8Bit(); + return result; + } + result.channelName = arg.mid(14).toLocal8Bit(); + if (result.channelName == "stdout") { + result.channel = stdout; + } else if (result.channelName == "stderr") { + result.channel = stderr; + } else { + result.exitCode = 3; + result.errorMessage = "Unknown channel " + result.channelName; + return result; + } + } else { + result.fileName = arg; + } + } + return result; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + const Args args = parseArguments(app.arguments()); + if (args.exitCode) { + fprintf(stderr, "testDetached: %s\n", args.errorMessage.constData()); + return args.exitCode; + } + + if (args.channel) { + QFile channel; + if (!channel.open(args.channel, QIODevice::WriteOnly | QIODevice::Text)) { + fprintf(stderr, "Cannot open channel %s for writing: %s\n", + qPrintable(args.channelName), qPrintable(channel.errorString())); + return 4; + } + writeStuff(channel); + } + + QFile f(args.fileName); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + fprintf(stderr, "Cannot open %s for writing: %s\n", + qPrintable(f.fileName()), qPrintable(f.errorString())); + return 1; + } + writeStuff(f); f.close(); return 0; diff --git a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp index f8268f5991..6b1516600f 100644 --- a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp +++ b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp @@ -126,6 +126,7 @@ private slots: void systemEnvironment(); void lockupsInStartDetached(); void waitForReadyReadForNonexistantProcess(); + void detachedProcessParameters_data(); void detachedProcessParameters(); void startFinishStartFinish(); void invalidProgramString_data(); @@ -2030,13 +2031,25 @@ void tst_QProcess::fileWriterProcess() } while (stopWatch.elapsed() < 3000); } +void tst_QProcess::detachedProcessParameters_data() +{ + QTest::addColumn("outChannel"); + QTest::newRow("none") << QString(); + QTest::newRow("stdout") << QString("stdout"); + QTest::newRow("stderr") << QString("stderr"); +} + void tst_QProcess::detachedProcessParameters() { + QFETCH(QString, outChannel); qint64 pid; QFile infoFile(m_temporaryDir.path() + QLatin1String("/detachedinfo.txt")); if (infoFile.exists()) QVERIFY(infoFile.remove()); + QFile channelFile(m_temporaryDir.path() + QLatin1String("detachedinfo2.txt")); + if (channelFile.exists()) + QVERIFY(channelFile.remove()); QString workingDir = QDir::currentPath() + "/testDetached"; @@ -2054,7 +2067,15 @@ void tst_QProcess::detachedProcessParameters() process.setCreateProcessArgumentsModifier( [&modifierCalls] (QProcess::CreateProcessArguments *) { modifierCalls++; }); #endif - process.setArguments(QStringList(infoFile.fileName())); + QStringList args(infoFile.fileName()); + if (!outChannel.isEmpty()) { + args << QStringLiteral("--out-channel=") + outChannel; + if (outChannel == "stdout") + process.setStandardOutputFile(channelFile.fileName()); + else if (outChannel == "stderr") + process.setStandardErrorFile(channelFile.fileName()); + } + process.setArguments(args); process.setWorkingDirectory(workingDir); process.setProcessEnvironment(environment); QVERIFY(process.startDetached(&pid)); @@ -2071,9 +2092,22 @@ void tst_QProcess::detachedProcessParameters() QString actualWorkingDir = QString::fromUtf8(infoFile.readLine()).trimmed(); QByteArray processIdString = infoFile.readLine().trimmed(); QByteArray actualEnvVarValue = infoFile.readLine().trimmed(); + QByteArray infoFileContent; + if (!outChannel.isEmpty()) { + infoFile.seek(0); + infoFileContent = infoFile.readAll(); + } infoFile.close(); infoFile.remove(); + if (!outChannel.isEmpty()) { + QVERIFY(channelFile.open(QIODevice::ReadOnly | QIODevice::Text)); + QByteArray channelContent = channelFile.readAll(); + channelFile.close(); + channelFile.remove(); + QCOMPARE(channelContent, infoFileContent); + } + bool ok = false; qint64 actualPid = processIdString.toLongLong(&ok); QVERIFY(ok); -- cgit v1.2.3