summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qprocess_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qprocess_win.cpp')
-rw-r--r--src/corelib/io/qprocess_win.cpp721
1 files changed, 346 insertions, 375 deletions
diff --git a/src/corelib/io/qprocess_win.cpp b/src/corelib/io/qprocess_win.cpp
index 148c6378c6..e64b133815 100644
--- a/src/corelib/io/qprocess_win.cpp
+++ b/src/corelib/io/qprocess_win.cpp
@@ -1,44 +1,11 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2017 Intel Corporation.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2017 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
//#define QPROCESS_DEBUG
+#include <qdebug.h>
+#include <private/qdebug_p.h>
+
#include "qprocess.h"
#include "qprocess_p.h"
#include "qwindowspipereader_p.h"
@@ -46,13 +13,12 @@
#include <qdatetime.h>
#include <qdir.h>
-#include <qelapsedtimer.h>
#include <qfileinfo.h>
#include <qrandom.h>
#include <qwineventnotifier.h>
+#include <qscopedvaluerollback.h>
#include <private/qsystemlibrary_p.h>
#include <private/qthread_p.h>
-#include <qdebug.h>
#include "private/qfsfileengine_p.h" // for longFileName
@@ -62,6 +28,10 @@
QT_BEGIN_NAMESPACE
+constexpr UINT KillProcessExitCode = 0xf291;
+
+using namespace Qt::StringLiterals;
+
QProcessEnvironment QProcessEnvironment::systemEnvironment()
{
QProcessEnvironment env;
@@ -86,14 +56,56 @@ QProcessEnvironment QProcessEnvironment::systemEnvironment()
#if QT_CONFIG(process)
-static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe)
+namespace {
+struct QProcessPoller
+{
+ QProcessPoller(const QProcessPrivate &proc);
+
+ int poll(const QDeadlineTimer &deadline);
+
+ enum { maxHandles = 4 };
+ HANDLE handles[maxHandles];
+ DWORD handleCount = 0;
+};
+
+QProcessPoller::QProcessPoller(const QProcessPrivate &proc)
+{
+ if (proc.stdinChannel.writer)
+ handles[handleCount++] = proc.stdinChannel.writer->syncEvent();
+ if (proc.stdoutChannel.reader)
+ handles[handleCount++] = proc.stdoutChannel.reader->syncEvent();
+ if (proc.stderrChannel.reader)
+ handles[handleCount++] = proc.stderrChannel.reader->syncEvent();
+
+ handles[handleCount++] = proc.pid->hProcess;
+}
+
+int QProcessPoller::poll(const QDeadlineTimer &deadline)
+{
+ DWORD waitRet;
+
+ do {
+ waitRet = WaitForMultipleObjectsEx(handleCount, handles, FALSE,
+ deadline.remainingTime(), TRUE);
+ } while (waitRet == WAIT_IO_COMPLETION);
+
+ if (waitRet - WAIT_OBJECT_0 < handleCount)
+ return 1;
+
+ return (waitRet == WAIT_TIMEOUT) ? 0 : -1;
+}
+} // anonymous namespace
+
+static bool qt_create_pipe(Q_PIPE *pipe, bool isInputPipe, BOOL defInheritFlag)
{
// Anomymous pipes do not support asynchronous I/O. Thus we
// create named pipes for redirecting stdout, stderr and stdin.
// The write handle must be non-inheritable for input pipes.
// The read handle must be non-inheritable for output pipes.
- SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), 0, false };
+ // When one process pipes to another (setStandardOutputProcess() was called),
+ // both handles must be inheritable (defInheritFlag == TRUE).
+ SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), 0, defInheritFlag };
HANDLE hServer;
wchar_t pipeName[256];
@@ -128,7 +140,8 @@ static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe)
DWORD dwError = GetLastError();
if (dwError != ERROR_PIPE_BUSY || !--attempts) {
qErrnoWarning(dwError, "QProcess: CreateNamedPipe failed.");
- return;
+ SetLastError(dwError);
+ return false;
}
}
@@ -142,9 +155,11 @@ static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe)
FILE_FLAG_OVERLAPPED,
NULL);
if (hClient == INVALID_HANDLE_VALUE) {
+ DWORD dwError = GetLastError();
qErrnoWarning("QProcess: CreateFile failed.");
CloseHandle(hServer);
- return;
+ SetLastError(dwError);
+ return false;
}
// Wait until connection is in place.
@@ -160,11 +175,13 @@ static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe)
WaitForSingleObject(overlapped.hEvent, INFINITE);
break;
default:
+ dwError = GetLastError();
qErrnoWarning(dwError, "QProcess: ConnectNamedPipe failed.");
CloseHandle(overlapped.hEvent);
CloseHandle(hClient);
CloseHandle(hServer);
- return;
+ SetLastError(dwError);
+ return false;
}
}
CloseHandle(overlapped.hEvent);
@@ -176,73 +193,47 @@ static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe)
pipe[0] = hServer;
pipe[1] = hClient;
}
-}
-
-static void duplicateStdWriteChannel(Q_PIPE *pipe, DWORD nStdHandle)
-{
- pipe[0] = INVALID_Q_PIPE;
- HANDLE hStdWriteChannel = GetStdHandle(nStdHandle);
- HANDLE hCurrentProcess = GetCurrentProcess();
- DuplicateHandle(hCurrentProcess, hStdWriteChannel, hCurrentProcess,
- &pipe[1], 0, TRUE, DUPLICATE_SAME_ACCESS);
+ return true;
}
/*
Create the pipes to a QProcessPrivate::Channel.
-
- This function must be called in order: stdin, stdout, stderr
*/
bool QProcessPrivate::openChannel(Channel &channel)
{
Q_Q(QProcess);
- if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) {
- return DuplicateHandle(GetCurrentProcess(), stdoutChannel.pipe[1], GetCurrentProcess(),
- &stderrChannel.pipe[1], 0, TRUE, DUPLICATE_SAME_ACCESS);
- }
-
switch (channel.type) {
- case Channel::Normal:
+ case Channel::Normal: {
// we're piping this channel to our own process
if (&channel == &stdinChannel) {
- if (inputChannelMode != QProcess::ForwardedInputChannel) {
- qt_create_pipe(channel.pipe, true);
- } else {
- channel.pipe[1] = INVALID_Q_PIPE;
- HANDLE hStdReadChannel = GetStdHandle(STD_INPUT_HANDLE);
- HANDLE hCurrentProcess = GetCurrentProcess();
- DuplicateHandle(hCurrentProcess, hStdReadChannel, hCurrentProcess,
- &channel.pipe[0], 0, TRUE, DUPLICATE_SAME_ACCESS);
+ if (!qt_create_pipe(channel.pipe, true, FALSE)) {
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
+ return false;
}
- } else {
- if (&channel == &stdoutChannel) {
- if (processChannelMode != QProcess::ForwardedChannels
- && processChannelMode != QProcess::ForwardedOutputChannel) {
- if (!stdoutChannel.reader) {
- stdoutChannel.reader = new QWindowsPipeReader(q);
- q->connect(stdoutChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardOutput()));
- }
- } else {
- duplicateStdWriteChannel(channel.pipe, STD_OUTPUT_HANDLE);
- }
- } else /* if (&channel == &stderrChannel) */ {
- if (processChannelMode != QProcess::ForwardedChannels
- && processChannelMode != QProcess::ForwardedErrorChannel) {
- if (!stderrChannel.reader) {
- stderrChannel.reader = new QWindowsPipeReader(q);
- q->connect(stderrChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardError()));
- }
- } else {
- duplicateStdWriteChannel(channel.pipe, STD_ERROR_HANDLE);
- }
+ return true;
+ }
+
+ if (&channel == &stdoutChannel) {
+ if (!stdoutChannel.reader) {
+ stdoutChannel.reader = new QWindowsPipeReader(q);
+ q->connect(stdoutChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardOutput()));
}
- if (channel.reader) {
- qt_create_pipe(channel.pipe, false);
- channel.reader->setHandle(channel.pipe[0]);
- channel.reader->startAsyncRead();
+ } else /* if (&channel == &stderrChannel) */ {
+ if (!stderrChannel.reader) {
+ stderrChannel.reader = new QWindowsPipeReader(q);
+ q->connect(stderrChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardError()));
}
}
+ if (!qt_create_pipe(channel.pipe, false, FALSE)) {
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
+ return false;
+ }
+
+ channel.reader->setHandle(channel.pipe[0]);
+ channel.reader->startAsyncRead();
return true;
+ }
case Channel::Redirect: {
// we're redirecting the channel to/from a file
SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
@@ -286,7 +277,6 @@ bool QProcessPrivate::openChannel(Channel &channel)
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Could not open output redirection for writing"));
}
- cleanup();
return false;
}
case Channel::PipeSource: {
@@ -297,22 +287,17 @@ bool QProcessPrivate::openChannel(Channel &channel)
if (source->pipe[1] != INVALID_Q_PIPE) {
// already constructed by the sink
- // make it inheritable
- HANDLE tmpHandle = source->pipe[1];
- if (!DuplicateHandle(GetCurrentProcess(), tmpHandle,
- GetCurrentProcess(), &source->pipe[1],
- 0, TRUE, DUPLICATE_SAME_ACCESS)) {
- return false;
- }
-
- CloseHandle(tmpHandle);
return true;
}
Q_ASSERT(source == &stdoutChannel);
Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink);
- qt_create_pipe(source->pipe, /* in = */ false); // source is stdout
+ if (!qt_create_pipe(source->pipe, /* in = */ false, TRUE)) { // source is stdout
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
+ return false;
+ }
+
sink->pipe[0] = source->pipe[0];
source->pipe[0] = INVALID_Q_PIPE;
@@ -325,21 +310,16 @@ bool QProcessPrivate::openChannel(Channel &channel)
if (sink->pipe[0] != INVALID_Q_PIPE) {
// already constructed by the source
- // make it inheritable
- HANDLE tmpHandle = sink->pipe[0];
- if (!DuplicateHandle(GetCurrentProcess(), tmpHandle,
- GetCurrentProcess(), &sink->pipe[0],
- 0, TRUE, DUPLICATE_SAME_ACCESS)) {
- return false;
- }
-
- CloseHandle(tmpHandle);
return true;
}
Q_ASSERT(sink == &stdinChannel);
Q_ASSERT(source->process == this && source->type == Channel::PipeSource);
- qt_create_pipe(sink->pipe, /* in = */ true); // sink is stdin
+ if (!qt_create_pipe(sink->pipe, /* in = */ true, TRUE)) { // sink is stdin
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
+ return false;
+ }
+
source->pipe[1] = sink->pipe[1];
sink->pipe[1] = INVALID_Q_PIPE;
@@ -361,23 +341,31 @@ void QProcessPrivate::destroyPipe(Q_PIPE pipe[2])
}
}
-template <class T>
-void deleteWorker(T *&worker)
+void QProcessPrivate::closeChannel(Channel *channel)
{
- if (!worker)
- return;
- worker->stop();
- worker->deleteLater();
- worker = nullptr;
+ if (channel == &stdinChannel) {
+ delete channel->writer;
+ channel->writer = nullptr;
+ } else {
+ delete channel->reader;
+ channel->reader = nullptr;
+ }
+ destroyPipe(channel->pipe);
}
-void QProcessPrivate::closeChannel(Channel *channel)
+void QProcessPrivate::cleanup()
{
- if (channel == &stdinChannel)
- deleteWorker(channel->writer);
- else
- deleteWorker(channel->reader);
- destroyPipe(channel->pipe);
+ q_func()->setProcessState(QProcess::NotRunning);
+
+ closeChannels();
+ delete processFinishedNotifier;
+ processFinishedNotifier = nullptr;
+ if (pid) {
+ CloseHandle(pid->hThread);
+ CloseHandle(pid->hProcess);
+ delete pid;
+ pid = nullptr;
+ }
}
static QString qt_create_commandline(const QString &program, const QStringList &arguments,
@@ -386,44 +374,44 @@ static QString qt_create_commandline(const QString &program, const QStringList &
QString args;
if (!program.isEmpty()) {
QString programName = program;
- if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' ')))
- programName = QLatin1Char('\"') + programName + QLatin1Char('\"');
- programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
+ if (!programName.startsWith(u'\"') && !programName.endsWith(u'\"') && programName.contains(u' '))
+ programName = u'\"' + programName + u'\"';
+ programName.replace(u'/', u'\\');
- // add the prgram as the first arg ... it works better
- args = programName + QLatin1Char(' ');
+ // add the program as the first arg ... it works better
+ args = programName + u' ';
}
- for (int i=0; i<arguments.size(); ++i) {
+ for (qsizetype i = 0; i < arguments.size(); ++i) {
QString tmp = arguments.at(i);
// Quotes are escaped and their preceding backslashes are doubled.
- int index = tmp.indexOf(QLatin1Char('"'));
+ qsizetype index = tmp.indexOf(u'"');
while (index >= 0) {
// Escape quote
- tmp.insert(index++, QLatin1Char('\\'));
+ tmp.insert(index++, u'\\');
// Double preceding backslashes (ignoring the one we just inserted)
- for (int i = index - 2 ; i >= 0 && tmp.at(i) == QLatin1Char('\\') ; --i) {
- tmp.insert(i, QLatin1Char('\\'));
+ for (qsizetype i = index - 2 ; i >= 0 && tmp.at(i) == u'\\' ; --i) {
+ tmp.insert(i, u'\\');
index++;
}
- index = tmp.indexOf(QLatin1Char('"'), index + 1);
+ index = tmp.indexOf(u'"', index + 1);
}
- if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
+ if (tmp.isEmpty() || tmp.contains(u' ') || tmp.contains(u'\t')) {
// The argument must not end with a \ since this would be interpreted
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
- int i = tmp.length();
- while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\'))
+ qsizetype i = tmp.length();
+ while (i > 0 && tmp.at(i - 1) == u'\\')
--i;
- tmp.insert(i, QLatin1Char('"'));
- tmp.prepend(QLatin1Char('"'));
+ tmp.insert(i, u'"');
+ tmp.prepend(u'"');
}
- args += QLatin1Char(' ') + tmp;
+ args += u' ' + tmp;
}
if (!nativeArguments.isEmpty()) {
if (!args.isEmpty())
- args += QLatin1Char(' ');
+ args += u' ';
args += nativeArguments;
}
@@ -433,63 +421,89 @@ static QString qt_create_commandline(const QString &program, const QStringList &
static QByteArray qt_create_environment(const QProcessEnvironmentPrivate::Map &environment)
{
QByteArray envlist;
- if (!environment.isEmpty()) {
- QProcessEnvironmentPrivate::Map copy = environment;
-
- // add PATH if necessary (for DLL loading)
- QProcessEnvironmentPrivate::Key pathKey(QLatin1String("PATH"));
- if (!copy.contains(pathKey)) {
- QByteArray path = qgetenv("PATH");
- if (!path.isEmpty())
- copy.insert(pathKey, QString::fromLocal8Bit(path));
- }
+ QProcessEnvironmentPrivate::Map copy = environment;
- // add systemroot if needed
- QProcessEnvironmentPrivate::Key rootKey(QLatin1String("SystemRoot"));
- if (!copy.contains(rootKey)) {
- QByteArray systemRoot = qgetenv("SystemRoot");
- if (!systemRoot.isEmpty())
- copy.insert(rootKey, QString::fromLocal8Bit(systemRoot));
- }
+ // add PATH if necessary (for DLL loading)
+ QProcessEnvironmentPrivate::Key pathKey("PATH"_L1);
+ if (!copy.contains(pathKey)) {
+ QByteArray path = qgetenv("PATH");
+ if (!path.isEmpty())
+ copy.insert(pathKey, QString::fromLocal8Bit(path));
+ }
- int pos = 0;
- auto it = copy.constBegin();
- const auto end = copy.constEnd();
+ // add systemroot if needed
+ QProcessEnvironmentPrivate::Key rootKey("SystemRoot"_L1);
+ if (!copy.contains(rootKey)) {
+ QByteArray systemRoot = qgetenv("SystemRoot");
+ if (!systemRoot.isEmpty())
+ copy.insert(rootKey, QString::fromLocal8Bit(systemRoot));
+ }
- static const wchar_t equal = L'=';
- static const wchar_t nul = L'\0';
+ qsizetype pos = 0;
+ auto it = copy.constBegin();
+ const auto end = copy.constEnd();
- for ( ; it != end; ++it) {
- uint tmpSize = sizeof(wchar_t) * (it.key().length() + it.value().length() + 2);
- // ignore empty strings
- if (tmpSize == sizeof(wchar_t) * 2)
- continue;
- envlist.resize(envlist.size() + tmpSize);
+ static const wchar_t equal = L'=';
+ static const wchar_t nul = L'\0';
- tmpSize = it.key().length() * sizeof(wchar_t);
- memcpy(envlist.data()+pos, it.key().utf16(), tmpSize);
- pos += tmpSize;
+ for (; it != end; ++it) {
+ qsizetype tmpSize = sizeof(wchar_t) * (it.key().length() + it.value().length() + 2);
+ // ignore empty strings
+ if (tmpSize == sizeof(wchar_t) * 2)
+ continue;
+ envlist.resize(envlist.size() + tmpSize);
- memcpy(envlist.data()+pos, &equal, sizeof(wchar_t));
- pos += sizeof(wchar_t);
+ tmpSize = it.key().length() * sizeof(wchar_t);
+ memcpy(envlist.data() + pos, it.key().data(), tmpSize);
+ pos += tmpSize;
- tmpSize = it.value().length() * sizeof(wchar_t);
- memcpy(envlist.data()+pos, it.value().utf16(), tmpSize);
- pos += tmpSize;
+ memcpy(envlist.data() + pos, &equal, sizeof(wchar_t));
+ pos += sizeof(wchar_t);
- memcpy(envlist.data()+pos, &nul, sizeof(wchar_t));
- pos += sizeof(wchar_t);
- }
- // add the 2 terminating 0 (actually 4, just to be on the safe side)
- envlist.resize( envlist.size()+4 );
- envlist[pos++] = 0;
- envlist[pos++] = 0;
- envlist[pos++] = 0;
- envlist[pos++] = 0;
+ tmpSize = it.value().length() * sizeof(wchar_t);
+ memcpy(envlist.data() + pos, it.value().data(), tmpSize);
+ pos += tmpSize;
+
+ memcpy(envlist.data() + pos, &nul, sizeof(wchar_t));
+ pos += sizeof(wchar_t);
}
+ // add the 2 terminating 0 (actually 4, just to be on the safe side)
+ envlist.resize(envlist.size() + 4);
+ envlist[pos++] = 0;
+ envlist[pos++] = 0;
+ envlist[pos++] = 0;
+ envlist[pos++] = 0;
+
return envlist;
}
+static Q_PIPE pipeOrStdHandle(Q_PIPE pipe, DWORD handleNumber)
+{
+ return pipe != INVALID_Q_PIPE ? pipe : GetStdHandle(handleNumber);
+}
+
+STARTUPINFOW QProcessPrivate::createStartupInfo()
+{
+ Q_PIPE stdinPipe = pipeOrStdHandle(stdinChannel.pipe[0], STD_INPUT_HANDLE);
+ Q_PIPE stdoutPipe = pipeOrStdHandle(stdoutChannel.pipe[1], STD_OUTPUT_HANDLE);
+ Q_PIPE stderrPipe = stderrChannel.pipe[1];
+ if (stderrPipe == INVALID_Q_PIPE) {
+ stderrPipe = (processChannelMode == QProcess::MergedChannels)
+ ? stdoutPipe
+ : GetStdHandle(STD_ERROR_HANDLE);
+ }
+
+ return STARTUPINFOW{
+ sizeof(STARTUPINFOW), 0, 0, 0,
+ (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
+ (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
+ 0, 0, 0,
+ STARTF_USESTDHANDLES,
+ 0, 0, 0,
+ stdinPipe, stdoutPipe, stderrPipe
+ };
+}
+
bool QProcessPrivate::callCreateProcess(QProcess::CreateProcessArguments *cpargs)
{
if (modifyCreateProcessArgs)
@@ -518,32 +532,21 @@ void QProcessPrivate::startProcess()
{
Q_Q(QProcess);
- bool success = false;
-
- if (pid) {
- CloseHandle(pid->hThread);
- CloseHandle(pid->hProcess);
- delete pid;
- pid = 0;
- }
pid = new PROCESS_INFORMATION;
memset(pid, 0, sizeof(PROCESS_INFORMATION));
q->setProcessState(QProcess::Starting);
- if (!openChannel(stdinChannel) ||
- !openChannel(stdoutChannel) ||
- !openChannel(stderrChannel)) {
- QString errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
+ if (!openChannels()) {
+ // openChannel sets the error string
+ Q_ASSERT(!errorString.isEmpty());
cleanup();
- setErrorAndEmit(QProcess::FailedToStart, errorString);
- q->setProcessState(QProcess::NotRunning);
return;
}
const QString args = qt_create_commandline(program, arguments, nativeArguments);
QByteArray envlist;
- if (environment.d.constData())
+ if (!environment.inheritsFromParent())
envlist = qt_create_environment(environment.d.constData()->vars);
#if defined QPROCESS_DEBUG
@@ -560,39 +563,31 @@ void QProcessPrivate::startProcess()
// create new console windows (behavior consistent with UNIX).
DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
- STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
- (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
- (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
- 0, 0, 0,
- STARTF_USESTDHANDLES,
- 0, 0, 0,
- stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1]
- };
-
+ STARTUPINFOW startupInfo = createStartupInfo();
const QString nativeWorkingDirectory = QDir::toNativeSeparators(workingDirectory);
QProcess::CreateProcessArguments cpargs = {
nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())),
nullptr, nullptr, true, dwCreationFlags,
- environment.isEmpty() ? nullptr : envlist.data(),
+ environment.inheritsFromParent() ? nullptr : envlist.data(),
nativeWorkingDirectory.isEmpty()
? nullptr : reinterpret_cast<const wchar_t *>(nativeWorkingDirectory.utf16()),
&startupInfo, pid
};
- success = callCreateProcess(&cpargs);
- QString errorString;
- if (!success) {
+ if (!callCreateProcess(&cpargs)) {
// Capture the error string before we do CloseHandle below
- errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
- }
-
- if (!success) {
+ QString errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
cleanup();
setErrorAndEmit(QProcess::FailedToStart, errorString);
- q->setProcessState(QProcess::NotRunning);
return;
}
+ // The pipe writer may have already been created before we had
+ // the pipe handle, specifically if the user wrote data from the
+ // stateChanged() slot.
+ if (stdinChannel.writer)
+ stdinChannel.writer->setHandle(stdinChannel.pipe[1]);
+
q->setProcessState(QProcess::Running);
// User can call kill()/terminate() from the stateChanged() slot
// so check before proceeding
@@ -654,10 +649,10 @@ void QProcessPrivate::terminateProcess()
void QProcessPrivate::killProcess()
{
if (pid)
- TerminateProcess(pid->hProcess, 0xf291);
+ TerminateProcess(pid->hProcess, KillProcessExitCode);
}
-bool QProcessPrivate::waitForStarted(int)
+bool QProcessPrivate::waitForStarted(const QDeadlineTimer &)
{
if (processStarted())
return true;
@@ -671,180 +666,180 @@ bool QProcessPrivate::waitForStarted(int)
bool QProcessPrivate::drainOutputPipes()
{
- if (!stdoutChannel.reader && !stderrChannel.reader)
- return false;
+ bool readyReadEmitted = false;
- bool someReadyReadEmitted = false;
- forever {
- bool readyReadEmitted = false;
- bool readOperationActive = false;
- if (stdoutChannel.reader) {
- readyReadEmitted |= stdoutChannel.reader->waitForReadyRead(0);
- readOperationActive = stdoutChannel.reader && stdoutChannel.reader->isReadOperationActive();
- }
- if (stderrChannel.reader) {
- readyReadEmitted |= stderrChannel.reader->waitForReadyRead(0);
- readOperationActive |= stderrChannel.reader && stderrChannel.reader->isReadOperationActive();
- }
- someReadyReadEmitted |= readyReadEmitted;
- if (!readOperationActive || !readyReadEmitted)
- break;
- QThread::yieldCurrentThread();
+ if (stdoutChannel.reader) {
+ stdoutChannel.reader->drainAndStop();
+ readyReadEmitted = _q_canReadStandardOutput();
+ }
+ if (stderrChannel.reader) {
+ stderrChannel.reader->drainAndStop();
+ readyReadEmitted |= _q_canReadStandardError();
}
- return someReadyReadEmitted;
+ return readyReadEmitted;
}
-bool QProcessPrivate::waitForReadyRead(int msecs)
+bool QProcessPrivate::waitForReadyRead(const QDeadlineTimer &deadline)
{
- QIncrementalSleepTimer timer(msecs);
-
forever {
- if (!writeBuffer.isEmpty() && !_q_canWrite())
+ QProcessPoller poller(*this);
+ int ret = poller.poll(deadline);
+ if (ret < 0)
return false;
- if (stdinChannel.writer && stdinChannel.writer->waitForWrite(0))
- timer.resetIncrements();
+ if (ret == 0)
+ break;
+
+ if (stdinChannel.writer)
+ stdinChannel.writer->checkForWrite();
- if ((stdoutChannel.reader && stdoutChannel.reader->waitForReadyRead(0))
- || (stderrChannel.reader && stderrChannel.reader->waitForReadyRead(0)))
+ if ((stdoutChannel.reader && stdoutChannel.reader->checkForReadyRead())
+ || (stderrChannel.reader && stderrChannel.reader->checkForReadyRead()))
return true;
if (!pid)
return false;
- if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) {
+
+ if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) {
bool readyReadEmitted = drainOutputPipes();
if (pid)
- _q_processDied();
+ processFinished();
return readyReadEmitted;
}
-
- Sleep(timer.nextSleepTime());
- if (timer.hasTimedOut())
- break;
}
setError(QProcess::Timedout);
return false;
}
-bool QProcessPrivate::waitForBytesWritten(int msecs)
+bool QProcessPrivate::waitForBytesWritten(const QDeadlineTimer &deadline)
{
- QIncrementalSleepTimer timer(msecs);
-
forever {
- bool pendingDataInPipe = stdinChannel.writer && stdinChannel.writer->bytesToWrite();
-
- // If we don't have pending data, and our write buffer is
- // empty, we fail.
- if (!pendingDataInPipe && writeBuffer.isEmpty())
+ // At entry into the loop the pipe writer's buffer can be empty to
+ // start with, in which case we fail immediately. Also, if the input
+ // pipe goes down somewhere in the code below, we avoid waiting for
+ // a full timeout.
+ if (!stdinChannel.writer || !stdinChannel.writer->isWriteOperationActive())
return false;
- // If we don't have pending data and we do have data in our
- // write buffer, try to flush that data over to the pipe
- // writer. Fail on error.
- if (!pendingDataInPipe) {
- if (!_q_canWrite())
- return false;
- }
+ QProcessPoller poller(*this);
+ int ret = poller.poll(deadline);
+ if (ret < 0)
+ return false;
+ if (ret == 0)
+ break;
- // Wait for the pipe writer to acknowledge that it has
- // written. This will succeed if either the pipe writer has
- // already written the data, or if it manages to write data
- // within the given timeout. If the write buffer was non-empty
- // and the stdinChannel.writer is now dead, that means _q_canWrite()
- // destroyed the writer after it successfully wrote the last
- // batch.
- if (!stdinChannel.writer || stdinChannel.writer->waitForWrite(0))
+ if (stdinChannel.writer->checkForWrite())
return true;
// If we wouldn't write anything, check if we can read stdout.
- if (stdoutChannel.pipe[0] != INVALID_Q_PIPE
- && bytesAvailableInChannel(&stdoutChannel) != 0) {
- tryReadFromChannel(&stdoutChannel);
- timer.resetIncrements();
- }
+ if (stdoutChannel.reader)
+ stdoutChannel.reader->checkForReadyRead();
// Check if we can read stderr.
- if (stderrChannel.pipe[0] != INVALID_Q_PIPE
- && bytesAvailableInChannel(&stderrChannel) != 0) {
- tryReadFromChannel(&stderrChannel);
- timer.resetIncrements();
- }
+ if (stderrChannel.reader)
+ stderrChannel.reader->checkForReadyRead();
// Check if the process died while reading.
if (!pid)
return false;
- // Wait for the process to signal any change in its state,
- // such as incoming data, or if the process died.
- if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) {
- _q_processDied();
+ // Check if the process is signaling completion.
+ if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) {
+ drainOutputPipes();
+ if (pid)
+ processFinished();
return false;
}
-
- // Only wait for as long as we've been asked.
- if (timer.hasTimedOut())
- break;
}
setError(QProcess::Timedout);
return false;
}
-bool QProcessPrivate::waitForFinished(int msecs)
+bool QProcessPrivate::waitForFinished(const QDeadlineTimer &deadline)
{
#if defined QPROCESS_DEBUG
- qDebug("QProcessPrivate::waitForFinished(%d)", msecs);
+ qDebug("QProcessPrivate::waitForFinished(%lld)", deadline.remainingTime());
#endif
- QIncrementalSleepTimer timer(msecs);
-
forever {
- if (!writeBuffer.isEmpty() && !_q_canWrite())
+ QProcessPoller poller(*this);
+ int ret = poller.poll(deadline);
+ if (ret < 0)
return false;
- if (stdinChannel.writer && stdinChannel.writer->waitForWrite(0))
- timer.resetIncrements();
- if (stdoutChannel.reader && stdoutChannel.reader->waitForReadyRead(0))
- timer.resetIncrements();
- if (stderrChannel.reader && stderrChannel.reader->waitForReadyRead(0))
- timer.resetIncrements();
-
- if (!pid) {
- drainOutputPipes();
+ if (ret == 0)
+ break;
+
+ if (stdinChannel.writer)
+ stdinChannel.writer->checkForWrite();
+ if (stdoutChannel.reader)
+ stdoutChannel.reader->checkForReadyRead();
+ if (stderrChannel.reader)
+ stderrChannel.reader->checkForReadyRead();
+
+ if (!pid)
return true;
- }
- if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) {
+ if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) {
drainOutputPipes();
if (pid)
- _q_processDied();
+ processFinished();
return true;
}
-
- if (timer.hasTimedOut())
- break;
}
setError(QProcess::Timedout);
return false;
}
-
void QProcessPrivate::findExitCode()
{
DWORD theExitCode;
Q_ASSERT(pid);
if (GetExitCodeProcess(pid->hProcess, &theExitCode)) {
exitCode = theExitCode;
- crashed = (exitCode == 0xf291 // our magic number, see killProcess
- || (theExitCode >= 0x80000000 && theExitCode < 0xD0000000));
+ if (exitCode == KillProcessExitCode
+ || (theExitCode >= 0x80000000 && theExitCode < 0xD0000000))
+ exitStatus = QProcess::CrashExit;
+ else
+ exitStatus = QProcess::NormalExit;
}
}
-void QProcessPrivate::flushPipeWriter()
+/*! \reimp
+ \internal
+*/
+qint64 QProcess::writeData(const char *data, qint64 len)
{
- if (stdinChannel.writer && stdinChannel.writer->bytesToWrite() > 0)
- stdinChannel.writer->waitForWrite(ULONG_MAX);
+ Q_D(QProcess);
+
+ if (d->stdinChannel.closed) {
+#if defined QPROCESS_DEBUG
+ qDebug("QProcess::writeData(%p \"%s\", %lld) == 0 (write channel closing)",
+ data, QtDebugUtils::toPrintable(data, len, 16).constData(), len);
+#endif
+ return 0;
+ }
+
+ if (!d->stdinChannel.writer) {
+ d->stdinChannel.writer = new QWindowsPipeWriter(d->stdinChannel.pipe[1], this);
+ QObjectPrivate::connect(d->stdinChannel.writer, &QWindowsPipeWriter::bytesWritten,
+ d, &QProcessPrivate::_q_bytesWritten);
+ QObjectPrivate::connect(d->stdinChannel.writer, &QWindowsPipeWriter::writeFailed,
+ d, &QProcessPrivate::_q_writeFailed);
+ }
+
+ if (d->isWriteChunkCached(data, len))
+ d->stdinChannel.writer->write(*(d->currentWriteChunk));
+ else
+ d->stdinChannel.writer->write(data, len);
+
+#if defined QPROCESS_DEBUG
+ qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)",
+ data, QtDebugUtils::toPrintable(data, len, 16).constData(), len, len);
+#endif
+ return len;
}
qint64 QProcessPrivate::pipeWriterBytesToWrite() const
@@ -852,23 +847,22 @@ qint64 QProcessPrivate::pipeWriterBytesToWrite() const
return stdinChannel.writer ? stdinChannel.writer->bytesToWrite() : qint64(0);
}
-bool QProcessPrivate::writeToStdin()
+void QProcessPrivate::_q_bytesWritten(qint64 bytes)
{
Q_Q(QProcess);
- if (!stdinChannel.writer) {
- stdinChannel.writer = new QWindowsPipeWriter(stdinChannel.pipe[1], q);
- QObject::connect(stdinChannel.writer, &QWindowsPipeWriter::bytesWritten,
- q, &QProcess::bytesWritten);
- QObjectPrivate::connect(stdinChannel.writer, &QWindowsPipeWriter::canWrite,
- this, &QProcessPrivate::_q_canWrite);
- } else {
- if (stdinChannel.writer->isWriteOperationActive())
- return true;
+ if (!emittedBytesWritten) {
+ QScopedValueRollback<bool> guard(emittedBytesWritten, true);
+ emit q->bytesWritten(bytes);
}
+ if (stdinChannel.closed && pipeWriterBytesToWrite() == 0)
+ closeWriteChannel();
+}
- stdinChannel.writer->write(writeBuffer.read());
- return true;
+void QProcessPrivate::_q_writeFailed()
+{
+ closeWriteChannel();
+ setErrorAndEmit(QProcess::WriteError);
}
// Use ShellExecuteEx() to trigger an UAC prompt when CreateProcess()fails
@@ -877,14 +871,6 @@ static bool startDetachedUacPrompt(const QString &programIn, const QStringList &
const QString &nativeArguments,
const QString &workingDir, qint64 *pid)
{
- typedef BOOL (WINAPI *ShellExecuteExType)(SHELLEXECUTEINFOW *);
-
- static const ShellExecuteExType shellExecuteEx = // XP ServicePack 1 onwards.
- reinterpret_cast<ShellExecuteExType>(QSystemLibrary::resolve(QLatin1String("shell32"),
- "ShellExecuteExW"));
- if (!shellExecuteEx)
- return false;
-
const QString args = qt_create_commandline(QString(), // needs arguments only
arguments, nativeArguments);
SHELLEXECUTEINFOW shellExecuteExInfo;
@@ -901,7 +887,7 @@ static bool startDetachedUacPrompt(const QString &programIn, const QStringList &
shellExecuteExInfo.lpDirectory = reinterpret_cast<LPCWSTR>(workingDir.utf16());
shellExecuteExInfo.nShow = SW_SHOWNORMAL;
- if (!shellExecuteEx(&shellExecuteExInfo))
+ if (!ShellExecuteExW(&shellExecuteExInfo))
return false;
if (pid)
*pid = qint64(GetProcessId(shellExecuteExInfo.hProcess));
@@ -909,21 +895,13 @@ static bool startDetachedUacPrompt(const QString &programIn, const QStringList &
return true;
}
-static Q_PIPE pipeOrStdHandle(Q_PIPE pipe, DWORD handleNumber)
-{
- return pipe != INVALID_Q_PIPE ? pipe : GetStdHandle(handleNumber);
-}
-
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);
+ if (!openChannelsForDetached()) {
+ // openChannel sets the error string
+ closeChannels();
return false;
}
@@ -933,24 +911,14 @@ bool QProcessPrivate::startDetached(qint64 *pid)
void *envPtr = nullptr;
QByteArray envlist;
- if (environment.d.constData()) {
+ if (!environment.inheritsFromParent()) {
envlist = qt_create_environment(environment.d.constData()->vars);
envPtr = envlist.data();
}
DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
- STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
- (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
- (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
- 0, 0, 0,
- STARTF_USESTDHANDLES,
- 0, 0, 0,
- pipeOrStdHandle(stdinChannel.pipe[0], STD_INPUT_HANDLE),
- pipeOrStdHandle(stdoutChannel.pipe[1], STD_OUTPUT_HANDLE),
- pipeOrStdHandle(stderrChannel.pipe[1], STD_ERROR_HANDLE)
- };
-
+ STARTUPINFOW startupInfo = createStartupInfo();
QProcess::CreateProcessArguments cpargs = {
nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())),
nullptr, nullptr, true, dwCreationFlags, envPtr,
@@ -975,10 +943,13 @@ bool QProcessPrivate::startDetached(qint64 *pid)
success = startDetachedUacPrompt(program, arguments, nativeArguments,
workingDirectory, pid);
}
+ if (!success) {
+ if (pid)
+ *pid = -1;
+ setErrorAndEmit(QProcess::FailedToStart);
+ }
- closeChannel(&stdinChannel);
- closeChannel(&stdoutChannel);
- closeChannel(&stderrChannel);
+ closeChannels();
return success;
}