| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| //#define QPROCESS_DEBUG |
| #include "qprocess.h" |
| #include "qprocess_p.h" |
| #include "qwindowspipereader_p.h" |
| #include "qwindowspipewriter_p.h" |
| |
| #include <qdatetime.h> |
| #include <qdir.h> |
| #include <qelapsedtimer.h> |
| #include <qfileinfo.h> |
| #include <qrandom.h> |
| #include <qwineventnotifier.h> |
| #include <private/qsystemlibrary_p.h> |
| #include <private/qthread_p.h> |
| #include <qdebug.h> |
| |
| #include "private/qfsfileengine_p.h" // for longFileName |
| |
| #ifndef PIPE_REJECT_REMOTE_CLIENTS |
| #define PIPE_REJECT_REMOTE_CLIENTS 0x08 |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| 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) |
| |
| static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe) |
| { |
| // 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 }; |
| |
| 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."); |
| return; |
| } |
| } |
| |
| 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) { |
| qErrnoWarning("QProcess: CreateFile failed."); |
| CloseHandle(hServer); |
| return; |
| } |
| |
| // 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: |
| qErrnoWarning(dwError, "QProcess: ConnectNamedPipe failed."); |
| CloseHandle(overlapped.hEvent); |
| CloseHandle(hClient); |
| CloseHandle(hServer); |
| return; |
| } |
| } |
| CloseHandle(overlapped.hEvent); |
| |
| if (isInputPipe) { |
| pipe[0] = hClient; |
| pipe[1] = hServer; |
| } else { |
| 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); |
| } |
| |
| /* |
| 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: |
| // 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); |
| } |
| } 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); |
| } |
| } |
| if (channel.reader) { |
| qt_create_pipe(channel.pipe, 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")); |
| } |
| cleanup(); |
| 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 |
| // 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 |
| 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 |
| // 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 |
| 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; |
| } |
| } |
| |
| template <class T> |
| void deleteWorker(T *&worker) |
| { |
| if (!worker) |
| return; |
| worker->stop(); |
| worker->deleteLater(); |
| worker = nullptr; |
| } |
| |
| void QProcessPrivate::closeChannel(Channel *channel) |
| { |
| if (channel == &stdinChannel) |
| deleteWorker(channel->writer); |
| else |
| deleteWorker(channel->reader); |
| destroyPipe(channel->pipe); |
| } |
| |
| 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(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) |
| programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); |
| programName.replace(QLatin1Char('/'), QLatin1Char('\\')); |
| |
| // add the prgram as the first arg ... it works better |
| args = programName + QLatin1Char(' '); |
| } |
| |
| for (int 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('"')); |
| while (index >= 0) { |
| // Escape quote |
| tmp.insert(index++, QLatin1Char('\\')); |
| // 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('\\')); |
| index++; |
| } |
| index = tmp.indexOf(QLatin1Char('"'), index + 1); |
| } |
| if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\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('\\')) |
| --i; |
| tmp.insert(i, QLatin1Char('"')); |
| tmp.prepend(QLatin1Char('"')); |
| } |
| args += QLatin1Char(' ') + tmp; |
| } |
| |
| if (!nativeArguments.isEmpty()) { |
| if (!args.isEmpty()) |
| args += QLatin1Char(' '); |
| args += nativeArguments; |
| } |
| |
| return args; |
| } |
| |
| 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)); |
| } |
| |
| // 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)); |
| } |
| |
| int 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) { |
| 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); |
| |
| tmpSize = it.key().length() * sizeof(wchar_t); |
| memcpy(envlist.data()+pos, it.key().utf16(), 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().utf16(), 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; |
| } |
| |
| 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 (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); |
| |
| 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()); |
| 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()) |
| 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 = { 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] |
| }; |
| |
| 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(), |
| nativeWorkingDirectory.isEmpty() |
| ? nullptr : reinterpret_cast<const wchar_t *>(nativeWorkingDirectory.utf16()), |
| &startupInfo, pid |
| }; |
| success = callCreateProcess(&cpargs); |
| |
| QString errorString; |
| if (!success) { |
| // Capture the error string before we do CloseHandle below |
| errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string()); |
| } |
| |
| if (!success) { |
| cleanup(); |
| setErrorAndEmit(QProcess::FailedToStart, errorString); |
| q->setProcessState(QProcess::NotRunning); |
| return; |
| } |
| |
| 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); |
| QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_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, 0xf291); |
| } |
| |
| bool QProcessPrivate::waitForStarted(int) |
| { |
| if (processStarted()) |
| return true; |
| |
| if (processError == QProcess::FailedToStart) |
| return false; |
| |
| setError(QProcess::Timedout); |
| return false; |
| } |
| |
| bool QProcessPrivate::drainOutputPipes() |
| { |
| if (!stdoutChannel.reader && !stderrChannel.reader) |
| return 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(); |
| } |
| |
| return someReadyReadEmitted; |
| } |
| |
| bool QProcessPrivate::waitForReadyRead(int msecs) |
| { |
| QIncrementalSleepTimer timer(msecs); |
| |
| forever { |
| if (!writeBuffer.isEmpty() && !_q_canWrite()) |
| return false; |
| if (stdinChannel.writer && stdinChannel.writer->waitForWrite(0)) |
| timer.resetIncrements(); |
| |
| if ((stdoutChannel.reader && stdoutChannel.reader->waitForReadyRead(0)) |
| || (stderrChannel.reader && stderrChannel.reader->waitForReadyRead(0))) |
| return true; |
| |
| if (!pid) |
| return false; |
| if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) { |
| bool readyReadEmitted = drainOutputPipes(); |
| if (pid) |
| _q_processDied(); |
| return readyReadEmitted; |
| } |
| |
| Sleep(timer.nextSleepTime()); |
| if (timer.hasTimedOut()) |
| break; |
| } |
| |
| setError(QProcess::Timedout); |
| return false; |
| } |
| |
| bool QProcessPrivate::waitForBytesWritten(int msecs) |
| { |
| 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()) |
| 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; |
| } |
| |
| // 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)) |
| 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(); |
| } |
| |
| // Check if we can read stderr. |
| if (stderrChannel.pipe[0] != INVALID_Q_PIPE |
| && bytesAvailableInChannel(&stderrChannel) != 0) { |
| tryReadFromChannel(&stderrChannel); |
| timer.resetIncrements(); |
| } |
| |
| // 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(); |
| 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) |
| { |
| #if defined QPROCESS_DEBUG |
| qDebug("QProcessPrivate::waitForFinished(%d)", msecs); |
| #endif |
| |
| QIncrementalSleepTimer timer(msecs); |
| |
| forever { |
| if (!writeBuffer.isEmpty() && !_q_canWrite()) |
| 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(); |
| return true; |
| } |
| |
| if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) { |
| drainOutputPipes(); |
| if (pid) |
| _q_processDied(); |
| 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)); |
| } |
| } |
| |
| void QProcessPrivate::flushPipeWriter() |
| { |
| if (stdinChannel.writer && stdinChannel.writer->bytesToWrite() > 0) |
| stdinChannel.writer->waitForWrite(ULONG_MAX); |
| } |
| |
| qint64 QProcessPrivate::pipeWriterBytesToWrite() const |
| { |
| return stdinChannel.writer ? stdinChannel.writer->bytesToWrite() : qint64(0); |
| } |
| |
| bool QProcessPrivate::writeToStdin() |
| { |
| 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; |
| } |
| |
| stdinChannel.writer->write(writeBuffer.read()); |
| return true; |
| } |
| |
| // 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) |
| { |
| 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; |
| 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 (!shellExecuteEx(&shellExecuteExInfo)) |
| return false; |
| if (pid) |
| *pid = qint64(GetProcessId(shellExecuteExInfo.hProcess)); |
| CloseHandle(shellExecuteExInfo.hProcess); |
| 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); |
| return false; |
| } |
| |
| QString args = qt_create_commandline(program, arguments, nativeArguments); |
| bool success = false; |
| PROCESS_INFORMATION pinfo; |
| |
| void *envPtr = nullptr; |
| QByteArray envlist; |
| if (environment.d.constData()) { |
| 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) |
| }; |
| |
| QProcess::CreateProcessArguments cpargs = { |
| nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())), |
| 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); |
| } |
| |
| closeChannel(&stdinChannel); |
| closeChannel(&stdoutChannel); |
| closeChannel(&stderrChannel); |
| return success; |
| } |
| |
| #endif // QT_CONFIG(process) |
| |
| QT_END_NAMESPACE |