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:
parent
424d9e9e56
commit
7ad55ca65f
@ -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]);
|
||||||
|
|
||||||
|
@ -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->processInformation);
|
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()
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user