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;
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
|
@ -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<wchar_t *>(const_cast<ushort *>(args.utf16())),
|
||||
nullptr, nullptr, false, dwCreationFlags, envPtr,
|
||||
nullptr, nullptr, inheritHandles, dwCreationFlags, envPtr,
|
||||
workingDirectory.isEmpty()
|
||||
? nullptr : reinterpret_cast<const wchar_t *>(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;
|
||||
}
|
||||
|
||||
|
@ -40,23 +40,8 @@
|
||||
#include <windows.h>
|
||||
#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;
|
||||
|
@ -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<QString>("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);
|
||||
|
Loading…
Reference in New Issue
Block a user