Revision control
++ // 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