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 <thiago.macieira@intel.com>
This commit is contained in:
Joerg Bornemann 2017-05-30 13:53:33 +02:00
parent 424d9e9e56
commit 7ad55ca65f
4 changed files with 175 additions and 36 deletions

View File

@ -915,6 +915,17 @@ bool QProcessPrivate::startDetached(qint64 *pid)
return false; 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(); pid_t childPid = fork();
if (childPid == 0) { if (childPid == 0) {
struct sigaction noaction; struct sigaction noaction;
@ -931,6 +942,18 @@ bool QProcessPrivate::startDetached(qint64 *pid)
if (doubleForkPid == 0) { if (doubleForkPid == 0) {
qt_safe_close(pidPipe[1]); 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 (!encodedWorkingDirectory.isEmpty()) {
if (QT_CHDIR(encodedWorkingDirectory.constData()) == -1) if (QT_CHDIR(encodedWorkingDirectory.constData()) == -1)
qWarning("QProcessPrivate::startDetached: failed to chdir to %s", encodedWorkingDirectory.constData()); qWarning("QProcessPrivate::startDetached: failed to chdir to %s", encodedWorkingDirectory.constData());
@ -992,6 +1015,9 @@ bool QProcessPrivate::startDetached(qint64 *pid)
::_exit(1); ::_exit(1);
} }
closeChannel(&stdinChannel);
closeChannel(&stdoutChannel);
closeChannel(&stderrChannel);
qt_safe_close(startedPipe[1]); qt_safe_close(startedPipe[1]);
qt_safe_close(pidPipe[1]); qt_safe_close(pidPipe[1]);

View File

@ -462,10 +462,24 @@ bool QProcessPrivate::callCreateProcess(QProcess::CreateProcessArguments *cpargs
{ {
if (modifyCreateProcessArgs) if (modifyCreateProcessArgs)
modifyCreateProcessArgs(cpargs); modifyCreateProcessArgs(cpargs);
return CreateProcess(cpargs->applicationName, cpargs->arguments, cpargs->processAttributes, bool success = CreateProcess(cpargs->applicationName, cpargs->arguments,
cpargs->threadAttributes, cpargs->inheritHandles, cpargs->flags, cpargs->processAttributes, cpargs->threadAttributes,
cpargs->environment, cpargs->currentDirectory, cpargs->startupInfo, cpargs->inheritHandles, cpargs->flags, cpargs->environment,
cpargs->currentDirectory, cpargs->startupInfo,
cpargs->processInformation); 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() void QProcessPrivate::startProcess()
@ -535,19 +549,6 @@ void QProcessPrivate::startProcess()
errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string()); 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) { if (!success) {
cleanup(); cleanup();
setErrorAndEmit(QProcess::FailedToStart, errorString); setErrorAndEmit(QProcess::FailedToStart, errorString);
@ -874,6 +875,15 @@ bool QProcessPrivate::startDetached(qint64 *pid)
{ {
static const DWORD errorElevationRequired = 740; 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); QString args = qt_create_commandline(program, arguments, nativeArguments);
bool success = false; bool success = false;
PROCESS_INFORMATION pinfo; PROCESS_INFORMATION pinfo;
@ -890,11 +900,18 @@ bool QProcessPrivate::startDetached(qint64 *pid)
STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
(ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
(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 = { QProcess::CreateProcessArguments cpargs = {
nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())), nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())),
nullptr, nullptr, false, dwCreationFlags, envPtr, nullptr, nullptr, inheritHandles, dwCreationFlags, envPtr,
workingDirectory.isEmpty() workingDirectory.isEmpty()
? nullptr : reinterpret_cast<const wchar_t *>(workingDirectory.utf16()), ? nullptr : reinterpret_cast<const wchar_t *>(workingDirectory.utf16()),
&startupInfo, &pinfo &startupInfo, &pinfo
@ -909,10 +926,17 @@ bool QProcessPrivate::startDetached(qint64 *pid)
} else if (GetLastError() == errorElevationRequired) { } else if (GetLastError() == errorElevationRequired) {
if (envPtr) if (envPtr)
qWarning("QProcess: custom environment will be ignored for detached elevated process."); 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, success = startDetachedUacPrompt(program, arguments, nativeArguments,
workingDirectory, pid); workingDirectory, pid);
} }
closeChannel(&stdinChannel);
closeChannel(&stdoutChannel);
closeChannel(&stderrChannel);
return success; return success;
} }

View File

@ -40,23 +40,8 @@
#include <windows.h> #include <windows.h>
#endif #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.write(QDir::currentPath().toUtf8());
f.putChar('\n'); f.putChar('\n');
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
@ -67,7 +52,77 @@ int main(int argc, char **argv)
f.putChar('\n'); f.putChar('\n');
f.write(qgetenv("tst_QProcess")); f.write(qgetenv("tst_QProcess"));
f.putChar('\n'); 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(); f.close();
return 0; return 0;

View File

@ -126,6 +126,7 @@ private slots:
void systemEnvironment(); void systemEnvironment();
void lockupsInStartDetached(); void lockupsInStartDetached();
void waitForReadyReadForNonexistantProcess(); void waitForReadyReadForNonexistantProcess();
void detachedProcessParameters_data();
void detachedProcessParameters(); void detachedProcessParameters();
void startFinishStartFinish(); void startFinishStartFinish();
void invalidProgramString_data(); void invalidProgramString_data();
@ -2030,13 +2031,25 @@ void tst_QProcess::fileWriterProcess()
} while (stopWatch.elapsed() < 3000); } while (stopWatch.elapsed() < 3000);
} }
void tst_QProcess::detachedProcessParameters_data()
{
QTest::addColumn<QString>("outChannel");
QTest::newRow("none") << QString();
QTest::newRow("stdout") << QString("stdout");
QTest::newRow("stderr") << QString("stderr");
}
void tst_QProcess::detachedProcessParameters() void tst_QProcess::detachedProcessParameters()
{ {
QFETCH(QString, outChannel);
qint64 pid; qint64 pid;
QFile infoFile(m_temporaryDir.path() + QLatin1String("/detachedinfo.txt")); QFile infoFile(m_temporaryDir.path() + QLatin1String("/detachedinfo.txt"));
if (infoFile.exists()) if (infoFile.exists())
QVERIFY(infoFile.remove()); QVERIFY(infoFile.remove());
QFile channelFile(m_temporaryDir.path() + QLatin1String("detachedinfo2.txt"));
if (channelFile.exists())
QVERIFY(channelFile.remove());
QString workingDir = QDir::currentPath() + "/testDetached"; QString workingDir = QDir::currentPath() + "/testDetached";
@ -2054,7 +2067,15 @@ void tst_QProcess::detachedProcessParameters()
process.setCreateProcessArgumentsModifier( process.setCreateProcessArgumentsModifier(
[&modifierCalls] (QProcess::CreateProcessArguments *) { modifierCalls++; }); [&modifierCalls] (QProcess::CreateProcessArguments *) { modifierCalls++; });
#endif #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.setWorkingDirectory(workingDir);
process.setProcessEnvironment(environment); process.setProcessEnvironment(environment);
QVERIFY(process.startDetached(&pid)); QVERIFY(process.startDetached(&pid));
@ -2071,9 +2092,22 @@ void tst_QProcess::detachedProcessParameters()
QString actualWorkingDir = QString::fromUtf8(infoFile.readLine()).trimmed(); QString actualWorkingDir = QString::fromUtf8(infoFile.readLine()).trimmed();
QByteArray processIdString = infoFile.readLine().trimmed(); QByteArray processIdString = infoFile.readLine().trimmed();
QByteArray actualEnvVarValue = infoFile.readLine().trimmed(); QByteArray actualEnvVarValue = infoFile.readLine().trimmed();
QByteArray infoFileContent;
if (!outChannel.isEmpty()) {
infoFile.seek(0);
infoFileContent = infoFile.readAll();
}
infoFile.close(); infoFile.close();
infoFile.remove(); 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; bool ok = false;
qint64 actualPid = processIdString.toLongLong(&ok); qint64 actualPid = processIdString.toLongLong(&ok);
QVERIFY(ok); QVERIFY(ok);