Revision control
Copy as Markdown
// 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
// Qt-Security score:critical reason:execute-external-code
//#define QPROCESS_DEBUG
#include <qdebug.h>
#include <private/qdebug_p.h>
#include "qprocess.h"
#include "qprocess_p.h"
#include "qwindowspipereader_p.h"
#include "qwindowspipewriter_p.h"
#include <qdatetime.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qrandom.h>
#include <qwineventnotifier.h>
#include <qscopedvaluerollback.h>
#include <private/qsystemlibrary_p.h>
#include <private/qthread_p.h>
#include "private/qfsfileengine_p.h" // for longFileName
#ifndef PIPE_REJECT_REMOTE_CLIENTS
#define PIPE_REJECT_REMOTE_CLIENTS 0x08
#endif
QT_BEGIN_NAMESPACE
constexpr UINT KillProcessExitCode = 0xf291;
using namespace Qt::StringLiterals;
QProcessEnvironment QProcessEnvironment::systemEnvironment()
{
QProcessEnvironment env;
// Calls to setenv() affect the low-level environment as well.
// This is not the case the other way round.
if (wchar_t *envStrings = GetEnvironmentStringsW()) {
for (const wchar_t *entry = envStrings; *entry; ) {
const int entryLen = int(wcslen(entry));
// + 1 to permit magic cmd variable names starting with =
if (const wchar_t *equal = wcschr(entry + 1, L'=')) {
int nameLen = equal - entry;
QString name = QString::fromWCharArray(entry, nameLen);
QString value = QString::fromWCharArray(equal + 1, entryLen - nameLen - 1);
env.d->vars.insert(QProcessEnvironmentPrivate::Key(name), value);
}
entry += entryLen + 1;
}
FreeEnvironmentStringsW(envStrings);
}
return env;
}
#if QT_CONFIG(process)
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.
// 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];
unsigned int attempts = 1000;
forever {
_snwprintf(pipeName, sizeof(pipeName) / sizeof(pipeName[0]),
L"\\\\.\\pipe\\qt-%lX-%X", long(QCoreApplication::applicationPid()),
QRandomGenerator::global()->generate());
DWORD dwOpenMode = FILE_FLAG_OVERLAPPED;
DWORD dwOutputBufferSize = 0;
DWORD dwInputBufferSize = 0;
const DWORD dwPipeBufferSize = 1024 * 1024;
if (isInputPipe) {
dwOpenMode |= PIPE_ACCESS_OUTBOUND;
dwOutputBufferSize = dwPipeBufferSize;
} else {
dwOpenMode |= PIPE_ACCESS_INBOUND;
dwInputBufferSize = dwPipeBufferSize;
}
DWORD dwPipeFlags = PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
hServer = CreateNamedPipe(pipeName,
dwOpenMode,
dwPipeFlags,
1, // only one pipe instance
dwOutputBufferSize,
dwInputBufferSize,
0,
&secAtt);
if (hServer != INVALID_HANDLE_VALUE)
break;
DWORD dwError = GetLastError();
if (dwError != ERROR_PIPE_BUSY || !--attempts) {
qErrnoWarning(dwError, "QProcess: CreateNamedPipe failed.");
SetLastError(dwError);
return false;
}
}
secAtt.bInheritHandle = TRUE;
const HANDLE hClient = CreateFile(pipeName,
(isInputPipe ? (GENERIC_READ | FILE_WRITE_ATTRIBUTES)
: GENERIC_WRITE),
0,
&secAtt,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (hClient == INVALID_HANDLE_VALUE) {
DWORD dwError = GetLastError();
qErrnoWarning("QProcess: CreateFile failed.");
CloseHandle(hServer);
SetLastError(dwError);
return false;
}
// Wait until connection is in place.
OVERLAPPED overlapped;
ZeroMemory(&overlapped, sizeof(overlapped));
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ConnectNamedPipe(hServer, &overlapped) == 0) {
DWORD dwError = GetLastError();
switch (dwError) {
case ERROR_PIPE_CONNECTED:
break;
case ERROR_IO_PENDING:
WaitForSingleObject(overlapped.hEvent, INFINITE);
break;
default:
dwError = GetLastError();
qErrnoWarning(dwError, "QProcess: ConnectNamedPipe failed.");
CloseHandle(overlapped.hEvent);
CloseHandle(hClient);
CloseHandle(hServer);
SetLastError(dwError);
return false;
}
}
CloseHandle(overlapped.hEvent);
if (isInputPipe) {
pipe[0] = hClient;
pipe[1] = hServer;
} else {
pipe[0] = hServer;
pipe[1] = hClient;
}
return true;
}
/*
Create the pipes to a QProcessPrivate::Channel.
*/
bool QProcessPrivate::openChannel(Channel &channel)
{
Q_Q(QProcess);
switch (channel.type) {
case Channel::Normal: {
// we're piping this channel to our own process
if (&channel == &stdinChannel) {
if (!qt_create_pipe(channel.pipe, true, FALSE)) {
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
return false;
}
return true;
}
// stdout or stderr
if (!channel.reader) {
auto receiver = &channel == &stdoutChannel ? &QProcessPrivate::_q_canReadStandardOutput
: &QProcessPrivate::_q_canReadStandardError;
channel.reader = new QWindowsPipeReader(q);
QObjectPrivate::connect(channel.reader, &QWindowsPipeReader::readyRead,
this, receiver);
}
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 };
if (&channel == &stdinChannel) {
// try to open in read-only mode
channel.pipe[1] = INVALID_Q_PIPE;
channel.pipe[0] =
CreateFile((const wchar_t*)QFSFileEnginePrivate::longFileName(channel.file).utf16(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&secAtt,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (channel.pipe[0] != INVALID_Q_PIPE)
return true;
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Could not open input redirection for reading"));
} else {
// open in write mode
channel.pipe[0] = INVALID_Q_PIPE;
channel.pipe[1] =
CreateFile((const wchar_t *)QFSFileEnginePrivate::longFileName(channel.file).utf16(),
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&secAtt,
channel.append ? OPEN_ALWAYS : CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (channel.pipe[1] != INVALID_Q_PIPE) {
if (channel.append) {
SetFilePointer(channel.pipe[1], 0, NULL, FILE_END);
}
return true;
}
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Could not open output redirection for writing"));
}
return false;
}
case Channel::PipeSource: {
Q_ASSERT_X(channel.process, "QProcess::start", "Internal error");
// we are the source
Channel *source = &channel;
Channel *sink = &channel.process->stdinChannel;
if (source->pipe[1] != INVALID_Q_PIPE) {
// already constructed by the sink
return true;
}
Q_ASSERT(source == &stdoutChannel);
Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink);
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;
return true;
}
case Channel::PipeSink: { // we are the sink;
Q_ASSERT_X(channel.process, "QProcess::start", "Internal error");
Channel *source = &channel.process->stdoutChannel;
Channel *sink = &channel;
if (sink->pipe[0] != INVALID_Q_PIPE) {
// already constructed by the source
return true;
}
Q_ASSERT(sink == &stdinChannel);
Q_ASSERT(source->process == this && source->type == Channel::PipeSource);
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;
return true;
}
} // switch (channel.type)
return false;
}
void QProcessPrivate::destroyPipe(Q_PIPE pipe[2])
{
if (pipe[0] != INVALID_Q_PIPE) {
CloseHandle(pipe[0]);
pipe[0] = INVALID_Q_PIPE;
}
if (pipe[1] != INVALID_Q_PIPE) {
CloseHandle(pipe[1]);
pipe[1] = INVALID_Q_PIPE;
}
}
void QProcessPrivate::closeChannel(Channel *channel)
{
if (channel == &stdinChannel) {
delete channel->writer;
channel->writer = nullptr;
} else {
delete channel->reader;
channel->reader = nullptr;
}
destroyPipe(channel->pipe);
}
void QProcessPrivate::cleanup()
{
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,
const QString &nativeArguments)
{
QString args;
if (!program.isEmpty()) {
QString programName = program;
if (!programName.startsWith(u'\"') && !programName.endsWith(u'\"') && programName.contains(u' '))
programName = u'\"' + programName + u'\"';
programName.replace(u'/', u'\\');
// add the program as the first arg ... it works better
args = programName + u' ';
}
for (qsizetype i = 0; i < arguments.size(); ++i) {
QString tmp = arguments.at(i);
// Quotes are escaped and their preceding backslashes are doubled.
qsizetype index = tmp.indexOf(u'"');
while (index >= 0) {
// Escape quote
tmp.insert(index++, u'\\');
// Double preceding backslashes (ignoring the one we just inserted)
for (qsizetype i = index - 2 ; i >= 0 && tmp.at(i) == u'\\' ; --i) {
tmp.insert(i, u'\\');
index++;
}
index = tmp.indexOf(u'"', index + 1);
}
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\"
qsizetype i = tmp.length();
while (i > 0 && tmp.at(i - 1) == u'\\')
--i;
tmp.insert(i, u'"');
tmp.prepend(u'"');
}
args += u' ' + tmp;
}
if (!nativeArguments.isEmpty()) {
if (!args.isEmpty())
args += u' ';
args += nativeArguments;
}
return args;
}
static QByteArray qt_create_environment(const QProcessEnvironmentPrivate::Map &environment)
{
QByteArray envlist;
QProcessEnvironmentPrivate::Map copy = environment;
// 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));
}
// 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));
}
qsizetype pos = 0;
auto it = copy.constBegin();
const auto end = copy.constEnd();
static const wchar_t equal = L'=';
static const wchar_t nul = L'\0';
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);
tmpSize = it.key().length() * sizeof(wchar_t);
memcpy(envlist.data() + pos, it.key().data(), tmpSize);
pos += tmpSize;
memcpy(envlist.data() + pos, &equal, sizeof(wchar_t));
pos += sizeof(wchar_t);
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)
modifyCreateProcessArgs(cpargs);
bool success = CreateProcess(cpargs->applicationName, cpargs->arguments,
cpargs->processAttributes, cpargs->threadAttributes,
cpargs->inheritHandles, cpargs->flags, cpargs->environment,
cpargs->currentDirectory, cpargs->startupInfo,
cpargs->processInformation);
if (!success) {
// don't CloseHandle here (we'll do that in cleanup()) so GetLastError()
// remains unmodified
return false;
}
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()
{
Q_Q(QProcess);
pid = new PROCESS_INFORMATION;
memset(pid, 0, sizeof(PROCESS_INFORMATION));
q->setProcessState(QProcess::Starting);
if (!openChannels()) {
// openChannel sets the error string
Q_ASSERT(!errorString.isEmpty());
cleanup();
return;
}
QString args = qt_create_commandline(program, arguments, nativeArguments);
QByteArray envlist;
if (!environment.inheritsFromParent())
envlist = qt_create_environment(environment.d.constData()->vars);
#if defined QPROCESS_DEBUG
qDebug("Creating process");
qDebug(" program : [%s]", program.toLatin1().constData());
qDebug(" args : %s", args.toLatin1().constData());
qDebug(" pass environment : %s", environment.isEmpty() ? "no" : "yes");
#endif
// We cannot unconditionally set the CREATE_NO_WINDOW flag, because this
// will render the stdout/stderr handles connected to a console useless
// (this typically affects ForwardedChannels mode).
// However, we also do not want console tools launched from a GUI app to
// create new console windows (behavior consistent with UNIX).
DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
STARTUPINFOW startupInfo = createStartupInfo();
const QString nativeWorkingDirectory = QDir::toNativeSeparators(workingDirectory);
QProcess::CreateProcessArguments cpargs = {
nullptr, reinterpret_cast<wchar_t *>(args.data_ptr().data()),
nullptr, nullptr, true, dwCreationFlags,
environment.inheritsFromParent() ? nullptr : envlist.data(),
nativeWorkingDirectory.isEmpty()
? nullptr : reinterpret_cast<const wchar_t *>(nativeWorkingDirectory.utf16()),
&startupInfo, pid
};
if (!callCreateProcess(&cpargs)) {
// Capture the error string before we do CloseHandle below
QString errorString = qt_error_string();
cleanup();
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Process failed to start: %1").arg(errorString));
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
if (!pid)
return;
if (threadData.loadRelaxed()->hasEventDispatcher()) {
processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);
QObjectPrivate::connect(processFinishedNotifier, &QWinEventNotifier::activated, this,
&QProcessPrivate::_q_processDied);
processFinishedNotifier->setEnabled(true);
}
_q_startupNotification();
}
bool QProcessPrivate::processStarted(QString * /*errorMessage*/)
{
return processState == QProcess::Running;
}
qint64 QProcessPrivate::bytesAvailableInChannel(const Channel *channel) const
{
Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE);
Q_ASSERT(channel->reader);
DWORD bytesAvail = channel->reader->bytesAvailable();
#if defined QPROCESS_DEBUG
qDebug("QProcessPrivate::bytesAvailableInChannel(%d) == %lld",
int(channel - &stdinChannel), qint64(bytesAvail));
#endif
return bytesAvail;
}
qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint64 maxlen)
{
Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE);
Q_ASSERT(channel->reader);
return channel->reader->read(data, maxlen);
}
static BOOL QT_WIN_CALLBACK qt_terminateApp(HWND hwnd, LPARAM procId)
{
DWORD currentProcId = 0;
GetWindowThreadProcessId(hwnd, ¤tProcId);
if (currentProcId == (DWORD)procId)
PostMessage(hwnd, WM_CLOSE, 0, 0);
return TRUE;
}
void QProcessPrivate::terminateProcess()
{
if (pid) {
EnumWindows(qt_terminateApp, (LPARAM)pid->dwProcessId);
PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0);
}
}
void QProcessPrivate::killProcess()
{
if (pid)
TerminateProcess(pid->hProcess, KillProcessExitCode);
}
bool QProcessPrivate::waitForStarted(const QDeadlineTimer &)
{
if (processStarted())
return true;
if (processError == QProcess::FailedToStart)
return false;
setError(QProcess::Timedout);
return false;
}
bool QProcessPrivate::drainOutputPipes()
{
bool readyReadEmitted = false;
if (stdoutChannel.reader) {
stdoutChannel.reader->drainAndStop();
readyReadEmitted = _q_canReadStandardOutput();
}
if (stderrChannel.reader) {
stderrChannel.reader->drainAndStop();
readyReadEmitted |= _q_canReadStandardError();
}
return readyReadEmitted;
}
bool QProcessPrivate::waitForReadyRead(const QDeadlineTimer &deadline)
{
forever {
QProcessPoller poller(*this);
int ret = poller.poll(deadline);
if (ret < 0)
return false;
if (ret == 0)
break;
if (stdinChannel.writer)
stdinChannel.writer->checkForWrite();
if ((stdoutChannel.reader && stdoutChannel.reader->checkForReadyRead())
|| (stderrChannel.reader && stderrChannel.reader->checkForReadyRead()))
return true;
if (!pid)
return false;
if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) {
bool readyReadEmitted = drainOutputPipes();
if (pid)
processFinished();
return readyReadEmitted;
}
}
setError(QProcess::Timedout);
return false;
}
bool QProcessPrivate::waitForBytesWritten(const QDeadlineTimer &deadline)
{
forever {
// 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;
QProcessPoller poller(*this);
int ret = poller.poll(deadline);
if (ret < 0)
return false;
if (ret == 0)
break;
if (stdinChannel.writer->checkForWrite())
return true;
// If we wouldn't write anything, check if we can read stdout.
if (stdoutChannel.reader)
stdoutChannel.reader->checkForReadyRead();
// Check if we can read stderr.
if (stderrChannel.reader)
stderrChannel.reader->checkForReadyRead();
// Check if the process died while reading.
if (!pid)
return false;
// Check if the process is signaling completion.
if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) {
drainOutputPipes();
if (pid)
processFinished();
return false;
}
}
setError(QProcess::Timedout);
return false;
}
bool QProcessPrivate::waitForFinished(const QDeadlineTimer &deadline)
{
#if defined QPROCESS_DEBUG
qDebug("QProcessPrivate::waitForFinished(%lld)", deadline.remainingTime());
#endif
forever {
QProcessPoller poller(*this);
int ret = poller.poll(deadline);
if (ret < 0)
return false;
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, 0) == WAIT_OBJECT_0) {
drainOutputPipes();
if (pid)
processFinished();
return true;
}
}
setError(QProcess::Timedout);
return false;
}
void QProcessPrivate::findExitCode()
{
DWORD theExitCode;
Q_ASSERT(pid);
if (GetExitCodeProcess(pid->hProcess, &theExitCode)) {
exitCode = theExitCode;
if (exitCode == KillProcessExitCode
|| (theExitCode >= 0x80000000 && theExitCode < 0xD0000000))
exitStatus = QProcess::CrashExit;
else
exitStatus = QProcess::NormalExit;
}
}
/*! \reimp
\internal
*/
qint64 QProcess::writeData(const char *data, qint64 len)
{
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
{
return stdinChannel.writer ? stdinChannel.writer->bytesToWrite() : qint64(0);
}
void QProcessPrivate::_q_bytesWritten(qint64 bytes)
{
Q_Q(QProcess);
if (!emittedBytesWritten) {
QScopedValueRollback<bool> guard(emittedBytesWritten, true);
emit q->bytesWritten(bytes);
}
if (stdinChannel.closed && pipeWriterBytesToWrite() == 0)
closeWriteChannel();
}
void QProcessPrivate::_q_writeFailed()
{
closeWriteChannel();
setErrorAndEmit(QProcess::WriteError);
}
// Use ShellExecuteEx() to trigger an UAC prompt when CreateProcess()fails
// with ERROR_ELEVATION_REQUIRED.
static bool startDetachedUacPrompt(const QString &programIn, const QStringList &arguments,
const QString &nativeArguments,
const QString &workingDir, qint64 *pid)
{
const QString args = qt_create_commandline(QString(), // needs arguments only
arguments, nativeArguments);
SHELLEXECUTEINFOW shellExecuteExInfo;
memset(&shellExecuteExInfo, 0, sizeof(SHELLEXECUTEINFOW));
shellExecuteExInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
shellExecuteExInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE | SEE_MASK_FLAG_NO_UI | SEE_MASK_CLASSNAME;
shellExecuteExInfo.lpClass = L"exefile";
shellExecuteExInfo.lpVerb = L"runas";
const QString program = QDir::toNativeSeparators(programIn);
shellExecuteExInfo.lpFile = reinterpret_cast<LPCWSTR>(program.utf16());
if (!args.isEmpty())
shellExecuteExInfo.lpParameters = reinterpret_cast<LPCWSTR>(args.utf16());
if (!workingDir.isEmpty())
shellExecuteExInfo.lpDirectory = reinterpret_cast<LPCWSTR>(workingDir.utf16());
shellExecuteExInfo.nShow = SW_SHOWNORMAL;
if (!ShellExecuteExW(&shellExecuteExInfo))
return false;
if (pid)
*pid = qint64(GetProcessId(shellExecuteExInfo.hProcess));
CloseHandle(shellExecuteExInfo.hProcess);
return true;
}
bool QProcessPrivate::startDetached(qint64 *pid)
{
static const DWORD errorElevationRequired = 740;
if (!openChannelsForDetached()) {
// openChannel sets the error string
closeChannels();
return false;
}
QString args = qt_create_commandline(program, arguments, nativeArguments);
bool success = false;
PROCESS_INFORMATION pinfo;
void *envPtr = nullptr;
QByteArray envlist;
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 = createStartupInfo();
QProcess::CreateProcessArguments cpargs = {
nullptr, reinterpret_cast<wchar_t *>(args.data_ptr().data()),
nullptr, nullptr, true, dwCreationFlags, envPtr,
workingDirectory.isEmpty()
? nullptr : reinterpret_cast<const wchar_t *>(workingDirectory.utf16()),
&startupInfo, &pinfo
};
success = callCreateProcess(&cpargs);
if (success) {
CloseHandle(pinfo.hThread);
CloseHandle(pinfo.hProcess);
if (pid)
*pid = pinfo.dwProcessId;
} 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);
}
if (!success) {
if (pid)
*pid = -1;
QString errorString = qt_error_string();
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Process failed to start: %1").arg(errorString));
}
closeChannels();
return success;
}
#endif // QT_CONFIG(process)
QT_END_NAMESPACE