diff options
author | hjk <hjk@qt.io> | 2019-07-24 09:08:58 +0200 |
---|---|---|
committer | hjk <hjk@qt.io> | 2019-07-25 06:38:26 +0000 |
commit | 404a4295ced4941072d95b20f5e60187a1d7c4a4 (patch) | |
tree | 07714b546a5cb534eed457da82464fb044f76b75 /src/libs/utils/consoleprocess.cpp | |
parent | 6a50abe34f51fc5fcdb0519c556392925ff4da09 (diff) |
Utils: Lump ConsoleProcess files together
... to reduce scope of conditionally compiled code which causes
regularly problems when forgetting about their special nature.
The code model isn't a big fan either.
Change-Id: I6a85b694b59a293daf4b9aab7c8e81c7ca284f0d
Reviewed-by: David Schulz <david.schulz@qt.io>
Diffstat (limited to 'src/libs/utils/consoleprocess.cpp')
-rw-r--r-- | src/libs/utils/consoleprocess.cpp | 839 |
1 files changed, 837 insertions, 2 deletions
diff --git a/src/libs/utils/consoleprocess.cpp b/src/libs/utils/consoleprocess.cpp index 335f9084c7e..9477361abde 100644 --- a/src/libs/utils/consoleprocess.cpp +++ b/src/libs/utils/consoleprocess.cpp @@ -23,10 +23,47 @@ ** ****************************************************************************/ -#include "consoleprocess_p.h" +#include "consoleprocess.h" + +#include <utils/algorithm.h> +#include <utils/environment.h> +#include <utils/hostosinfo.h> +#include <utils/qtcassert.h> +#include <utils/qtcprocess.h> +#include <utils/winutils.h> + +#include <QAbstractEventDispatcher> +#include <QCoreApplication> +#include <QCoreApplication> +#include <QDir> +#include <QFileInfo> +#include <QLocalServer> +#include <QLocalSocket> +#include <QSettings> +#include <QTemporaryFile> +#include <QTimer> +#include <QWinEventNotifier> + +#ifdef Q_OS_WIN + +# include <windows.h> +# include <stdlib.h> +# include <cstring> + +#else + +# include <sys/stat.h> +# include <sys/types.h> +# include <errno.h> +# include <string.h> +# include <unistd.h> + +#endif namespace Utils { +// TerminalCommand + TerminalCommand::TerminalCommand(const QString &command, const QString &openArgs, const QString &executeArgs) : command(command) , openArgs(openArgs) @@ -34,12 +71,810 @@ TerminalCommand::TerminalCommand(const QString &command, const QString &openArgs { } +// ConsoleProcessPrivate + +class ConsoleProcessPrivate +{ +public: + ConsoleProcessPrivate() = default; + + static QString m_defaultConsoleProcess; + ConsoleProcess::Mode m_mode = ConsoleProcess::Run; + QString m_workingDir; + Environment m_environment; + qint64 m_appPid = 0; + int m_appCode; + CommandLine m_commandLine; + QProcess::ExitStatus m_appStatus; + QLocalServer m_stubServer; + QLocalSocket *m_stubSocket = nullptr; + QTemporaryFile *m_tempFile = nullptr; + QProcess::ProcessError m_error = QProcess::UnknownError; + QString m_errorString; + bool m_abortOnMetaChars = true; + QSettings *m_settings = nullptr; + + // Used on Unix only + QProcess m_process; + bool m_stubConnected = false; + QTimer *m_stubConnectTimer = nullptr; + QByteArray m_stubServerDir; + qint64 m_stubPid = 0; + + // Used on Windows only + qint64 m_appMainThreadId = 0; + +#ifdef Q_OS_WIN + PROCESS_INFORMATION *m_pid = nullptr; + HANDLE m_hInferior = NULL; + QWinEventNotifier *inferiorFinishedNotifier = nullptr; + QWinEventNotifier *processFinishedNotifier = nullptr; +#endif +}; + + +// ConsoleProcess + +ConsoleProcess::ConsoleProcess(QObject *parent) : + QObject(parent), d(new ConsoleProcessPrivate) +{ + connect(&d->m_stubServer, &QLocalServer::newConnection, + this, &ConsoleProcess::stubConnectionAvailable); + + d->m_process.setProcessChannelMode(QProcess::ForwardedChannels); +} + ConsoleProcess::~ConsoleProcess() { stop(); delete d; } +void ConsoleProcess::setCommand(const CommandLine &command) +{ + d->m_commandLine = command; +} + +void ConsoleProcess::setSettings(QSettings *settings) +{ + d->m_settings = settings; +} + +Q_GLOBAL_STATIC_WITH_ARGS(const QVector<TerminalCommand>, knownTerminals, ( +{ + {"x-terminal-emulator", "", "-e"}, + {"xterm", "", "-e"}, + {"aterm", "", "-e"}, + {"Eterm", "", "-e"}, + {"rxvt", "", "-e"}, + {"urxvt", "", "-e"}, + {"xfce4-terminal", "", "-x"}, + {"konsole", "--separate", "-e"}, + {"gnome-terminal", "", "--"} +})); + +TerminalCommand ConsoleProcess::defaultTerminalEmulator() +{ + static TerminalCommand defaultTerm; + + if (defaultTerm.command.isEmpty()) { + + if (HostOsInfo::isMacHost()) { + const QString termCmd = QCoreApplication::applicationDirPath() + + "/../Resources/scripts/openTerminal.py"; + if (QFileInfo::exists(termCmd)) + defaultTerm = {termCmd, "", ""}; + else + defaultTerm = {"/usr/X11/bin/xterm", "", "-e"}; + + } else if (HostOsInfo::isAnyUnixHost()) { + const Environment env = Environment::systemEnvironment(); + for (const TerminalCommand &term : *knownTerminals) { + const QString result = env.searchInPath(term.command).toString(); + if (!result.isEmpty()) + defaultTerm = {result, term.openArgs, term.executeArgs}; + else + defaultTerm = {"xterm", "", "-e"}; + } + } + } + + return defaultTerm; +} + +QVector<TerminalCommand> ConsoleProcess::availableTerminalEmulators() +{ + QVector<TerminalCommand> result; + + if (HostOsInfo::isAnyUnixHost()) { + const Environment env = Environment::systemEnvironment(); + for (const TerminalCommand &term : *knownTerminals) { + const QString command = env.searchInPath(term.command).toString(); + if (!command.isEmpty()) + result.push_back({command, term.openArgs, term.executeArgs}); + } + // sort and put default terminal on top + const TerminalCommand defaultTerm = defaultTerminalEmulator(); + result.removeAll(defaultTerm); + sort(result); + result.prepend(defaultTerm); + } + + return result; +} + +const char kTerminalVersion[] = "4.8"; +const char kTerminalVersionKey[] = "General/Terminal/SettingsVersion"; +const char kTerminalCommandKey[] = "General/Terminal/Command"; +const char kTerminalOpenOptionsKey[] = "General/Terminal/OpenOptions"; +const char kTerminalExecuteOptionsKey[] = "General/Terminal/ExecuteOptions"; + +TerminalCommand ConsoleProcess::terminalEmulator(const QSettings *settings) +{ + if (settings && HostOsInfo::isAnyUnixHost()) { + if (settings->value(kTerminalVersionKey).toString() == kTerminalVersion) { + if (settings->contains(kTerminalCommandKey)) + return {settings->value(kTerminalCommandKey).toString(), + settings->value(kTerminalOpenOptionsKey).toString(), + settings->value(kTerminalExecuteOptionsKey).toString()}; + } else { + // TODO remove reading of old settings some time after 4.8 + const QString value = settings->value("General/TerminalEmulator").toString().trimmed(); + if (!value.isEmpty()) { + // split off command and options + const QStringList splitCommand = QtcProcess::splitArgs(value); + if (QTC_GUARD(!splitCommand.isEmpty())) { + const QString command = splitCommand.first(); + const QStringList quotedArgs = Utils::transform(splitCommand.mid(1), + &QtcProcess::quoteArgUnix); + const QString options = quotedArgs.join(' '); + return {command, "", options}; + } + } + } + } + + return defaultTerminalEmulator(); +} + +void ConsoleProcess::setTerminalEmulator(QSettings *settings, const TerminalCommand &term) +{ + if (HostOsInfo::isAnyUnixHost()) { + settings->setValue(kTerminalVersionKey, kTerminalVersion); + if (term == defaultTerminalEmulator()) { + settings->remove(kTerminalCommandKey); + settings->remove(kTerminalOpenOptionsKey); + settings->remove(kTerminalExecuteOptionsKey); + } else { + settings->setValue(kTerminalCommandKey, term.command); + settings->setValue(kTerminalOpenOptionsKey, term.openArgs); + settings->setValue(kTerminalExecuteOptionsKey, term.executeArgs); + } + } +} + +static QString quoteWinCommand(const QString &program) +{ + const QChar doubleQuote = QLatin1Char('"'); + + // add the program as the first arg ... it works better + QString programName = program; + programName.replace(QLatin1Char('/'), QLatin1Char('\\')); + if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote) + && programName.contains(QLatin1Char(' '))) { + programName.prepend(doubleQuote); + programName.append(doubleQuote); + } + return programName; +} + +static QString quoteWinArgument(const QString &arg) +{ + if (!arg.length()) + return QString::fromLatin1("\"\""); + + QString ret(arg); + // Quotes are escaped and their preceding backslashes are doubled. + ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\"")); + if (ret.contains(QRegExp(QLatin1String("\\s")))) { + // 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 = ret.length(); + while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) + --i; + ret.insert(i, QLatin1Char('"')); + ret.prepend(QLatin1Char('"')); + } + return ret; +} + +// Quote a Windows command line correctly for the "CreateProcess" API +QString createWinCommandline(const QString &program, const QStringList &args) +{ + QString programName = quoteWinCommand(program); + foreach (const QString &arg, args) { + programName += QLatin1Char(' '); + programName += quoteWinArgument(arg); + } + return programName; +} + +QString createWinCommandline(const QString &program, const QString &args) +{ + QString programName = quoteWinCommand(program); + if (!args.isEmpty()) { + programName += QLatin1Char(' '); + programName += args; + } + return programName; +} + + +bool ConsoleProcess::startTerminalEmulator(QSettings *settings, const QString &workingDir, + const Environment &env) +{ +#ifdef Q_OS_WIN + Q_UNUSED(settings) + + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + PROCESS_INFORMATION pinfo; + ZeroMemory(&pinfo, sizeof(pinfo)); + + QString cmdLine = createWinCommandline( + QString::fromLocal8Bit(qgetenv("COMSPEC")), QString()); + // cmdLine is assumed to be detached - + // https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083 + + QString totalEnvironment = env.toStringList().join('\0') + '\0'; + LPVOID envPtr = (env != Environment::systemEnvironment()) + ? (WCHAR *)(totalEnvironment.utf16()) : nullptr; + + bool success = CreateProcessW(0, (WCHAR *)cmdLine.utf16(), + 0, 0, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, + envPtr, workingDir.isEmpty() ? 0 : (WCHAR *)workingDir.utf16(), + &si, &pinfo); + + if (success) { + CloseHandle(pinfo.hThread); + CloseHandle(pinfo.hProcess); + } + + return success; +#else + const TerminalCommand term = terminalEmulator(settings); + QProcess process; + process.setProgram(term.command); + process.setArguments(QtcProcess::splitArgs(term.openArgs)); + process.setProcessEnvironment(env.toProcessEnvironment()); + process.setWorkingDirectory(workingDir); + + return process.startDetached(); +#endif +} + +void ConsoleProcess::setAbortOnMetaChars(bool abort) +{ + d->m_abortOnMetaChars = abort; +} + +qint64 ConsoleProcess::applicationMainThreadID() const +{ + if (HostOsInfo::isWindowsHost()) + return d->m_appMainThreadId; + return -1; +} + +bool ConsoleProcess::start() +{ + if (isRunning()) + return false; + + d->m_errorString.clear(); + d->m_error = QProcess::UnknownError; + +#ifdef Q_OS_WIN + + QString pcmd; + QString pargs; + if (d->m_mode != Run) { // The debugger engines already pre-process the arguments. + pcmd = d->m_commandLine.executable().toString(); + pargs = d->m_commandLine.arguments(); + } else { + QtcProcess::Arguments outArgs; + QtcProcess::prepareCommand(d->m_commandLine.executable().toString(), + d->m_commandLine.arguments(), + &pcmd, &outArgs, OsTypeWindows, + &d->m_environment, &d->m_workingDir); + pargs = outArgs.toWindowsArgs(); + } + + const QString err = stubServerListen(); + if (!err.isEmpty()) { + emitError(QProcess::FailedToStart, msgCommChannelFailed(err)); + return false; + } + + QStringList env = d->m_environment.toStringList(); + if (!env.isEmpty()) { + d->m_tempFile = new QTemporaryFile(); + if (!d->m_tempFile->open()) { + stubServerShutdown(); + emitError(QProcess::FailedToStart, msgCannotCreateTempFile(d->m_tempFile->errorString())); + delete d->m_tempFile; + d->m_tempFile = nullptr; + return false; + } + QTextStream out(d->m_tempFile); + out.setCodec("UTF-16LE"); + out.setGenerateByteOrderMark(false); + + // Add PATH and SystemRoot environment variables in case they are missing + const QStringList fixedEnvironment = [env] { + QStringList envStrings = env; + // add PATH if necessary (for DLL loading) + if (envStrings.filter(QRegExp(QLatin1String("^PATH="),Qt::CaseInsensitive)).isEmpty()) { + QByteArray path = qgetenv("PATH"); + if (!path.isEmpty()) + envStrings.prepend(QString::fromLatin1("PATH=%1").arg(QString::fromLocal8Bit(path))); + } + // add systemroot if needed + if (envStrings.filter(QRegExp(QLatin1String("^SystemRoot="),Qt::CaseInsensitive)).isEmpty()) { + QByteArray systemRoot = qgetenv("SystemRoot"); + if (!systemRoot.isEmpty()) + envStrings.prepend(QString::fromLatin1("SystemRoot=%1").arg(QString::fromLocal8Bit(systemRoot))); + } + return envStrings; + }(); + + for (const QString &var : fixedEnvironment) + out << var << QChar(0); + out << QChar(0); + out.flush(); + if (out.status() != QTextStream::Ok) { + stubServerShutdown(); + emitError(QProcess::FailedToStart, msgCannotWriteTempFile()); + delete d->m_tempFile; + d->m_tempFile = nullptr; + return false; + } + } + + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + d->m_pid = new PROCESS_INFORMATION; + ZeroMemory(d->m_pid, sizeof(PROCESS_INFORMATION)); + + QString workDir = QDir::toNativeSeparators(workingDirectory()); + if (!workDir.isEmpty() && !workDir.endsWith(QLatin1Char('\\'))) + workDir.append(QLatin1Char('\\')); + + QStringList stubArgs; + stubArgs << modeOption(d->m_mode) + << d->m_stubServer.fullServerName() + << workDir + << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) + << createWinCommandline(pcmd, pargs) + << msgPromptToClose(); + + const QString cmdLine = createWinCommandline( + QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_process_stub.exe"), stubArgs); + + bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(), + 0, 0, FALSE, CREATE_NEW_CONSOLE, + 0, 0, + &si, d->m_pid); + + if (!success) { + delete d->m_pid; + d->m_pid = nullptr; + delete d->m_tempFile; + d->m_tempFile = nullptr; + stubServerShutdown(); + emitError(QProcess::FailedToStart, tr("The process \"%1\" could not be started: %2").arg(cmdLine, winErrorMessage(GetLastError()))); + return false; + } + + d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this); + connect(d->processFinishedNotifier, &QWinEventNotifier::activated, + this, &ConsoleProcess::stubExited); + +#else + + QtcProcess::SplitError perr; + QtcProcess::Arguments pargs = QtcProcess::prepareArgs(d->m_commandLine.arguments(), + &perr, + HostOsInfo::hostOs(), + &d->m_environment, + &d->m_workingDir, + d->m_abortOnMetaChars); + + QString pcmd; + if (perr == QtcProcess::SplitOk) { + pcmd = d->m_commandLine.executable().toString(); + } else { + if (perr != QtcProcess::FoundMeta) { + emitError(QProcess::FailedToStart, tr("Quoting error in command.")); + return false; + } + if (d->m_mode == Debug) { + // FIXME: QTCREATORBUG-2809 + emitError(QProcess::FailedToStart, tr("Debugging complex shell commands in a terminal" + " is currently not supported.")); + return false; + } + pcmd = QLatin1String("/bin/sh"); + pargs = QtcProcess::Arguments::createUnixArgs( + {"-c", (QtcProcess::quoteArg(d->m_commandLine.executable().toString()) + + ' ' + d->m_commandLine.arguments())}); + } + + QtcProcess::SplitError qerr; + const TerminalCommand terminal = terminalEmulator(d->m_settings); + const QtcProcess::Arguments terminalArgs = QtcProcess::prepareArgs(terminal.executeArgs, + &qerr, + HostOsInfo::hostOs(), + &d->m_environment, + &d->m_workingDir); + if (qerr != QtcProcess::SplitOk) { + emitError(QProcess::FailedToStart, qerr == QtcProcess::BadQuoting + ? tr("Quoting error in terminal command.") + : tr("Terminal command may not be a shell command.")); + return false; + } + + const QString err = stubServerListen(); + if (!err.isEmpty()) { + emitError(QProcess::FailedToStart, msgCommChannelFailed(err)); + return false; + } + + d->m_environment.unset(QLatin1String("TERM")); + const QStringList env = d->m_environment.toStringList(); + if (!env.isEmpty()) { + d->m_tempFile = new QTemporaryFile(); + if (!d->m_tempFile->open()) { + stubServerShutdown(); + emitError(QProcess::FailedToStart, msgCannotCreateTempFile(d->m_tempFile->errorString())); + delete d->m_tempFile; + d->m_tempFile = nullptr; + return false; + } + QByteArray contents; + for (const QString &var : env) { + QByteArray l8b = var.toLocal8Bit(); + contents.append(l8b.constData(), l8b.size() + 1); + } + if (d->m_tempFile->write(contents) != contents.size() || !d->m_tempFile->flush()) { + stubServerShutdown(); + emitError(QProcess::FailedToStart, msgCannotWriteTempFile()); + delete d->m_tempFile; + d->m_tempFile = nullptr; + return false; + } + } + + const QString stubPath = QCoreApplication::applicationDirPath() + + QLatin1String("/" QTC_REL_TOOLS_PATH "/qtcreator_process_stub"); + const QStringList allArgs = terminalArgs.toUnixArgs() + << stubPath + << modeOption(d->m_mode) + << d->m_stubServer.fullServerName() + << msgPromptToClose() + << workingDirectory() + << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) + << QString::number(getpid()) + << pcmd + << pargs.toUnixArgs(); + + d->m_process.start(terminal.command, allArgs); + if (!d->m_process.waitForStarted()) { + stubServerShutdown(); + emitError(QProcess::UnknownError, tr("Cannot start the terminal emulator \"%1\", change the setting in the " + "Environment options.").arg(terminal.command)); + delete d->m_tempFile; + d->m_tempFile = nullptr; + return false; + } + d->m_stubConnectTimer = new QTimer(this); + connect(d->m_stubConnectTimer, &QTimer::timeout, this, &ConsoleProcess::stop); + d->m_stubConnectTimer->setSingleShot(true); + d->m_stubConnectTimer->start(10000); + +#endif + + return true; +} + +void ConsoleProcess::killProcess() +{ +#ifdef Q_OS_WIN + if (d->m_hInferior != NULL) { + TerminateProcess(d->m_hInferior, (unsigned)-1); + cleanupInferior(); + } +#else + if (d->m_stubSocket && d->m_stubSocket->isWritable()) { + d->m_stubSocket->write("k", 1); + d->m_stubSocket->flush(); + } + d->m_appPid = 0; +#endif +} + +void ConsoleProcess::killStub() +{ +#ifdef Q_OS_WIN + if (d->m_pid) { + TerminateProcess(d->m_pid->hProcess, (unsigned)-1); + WaitForSingleObject(d->m_pid->hProcess, INFINITE); + cleanupStub(); + } +#else + if (d->m_stubSocket && d->m_stubSocket->isWritable()) { + d->m_stubSocket->write("s", 1); + d->m_stubSocket->flush(); + } + stubServerShutdown(); + d->m_stubPid = 0; +#endif +} + +void ConsoleProcess::stop() +{ + killProcess(); + killStub(); + if (isRunning() && HostOsInfo::isAnyUnixHost()) { + d->m_process.terminate(); + if (!d->m_process.waitForFinished(1000) && d->m_process.state() == QProcess::Running) { + d->m_process.kill(); + d->m_process.waitForFinished(); + } + } +} + +bool ConsoleProcess::isRunning() const +{ +#ifdef Q_OS_WIN + return d->m_pid != nullptr; +#else + return d->m_process.state() != QProcess::NotRunning + || (d->m_stubSocket && d->m_stubSocket->isOpen()); +#endif +} + +QString ConsoleProcess::stubServerListen() +{ +#ifdef Q_OS_WIN + if (d->m_stubServer.listen(QString::fromLatin1("creator-%1-%2") + .arg(QCoreApplication::applicationPid()) + .arg(rand()))) + return QString(); + return d->m_stubServer.errorString(); +#else + // We need to put the socket in a private directory, as some systems simply do not + // check the file permissions of sockets. + QString stubFifoDir; + while (true) { + { + QTemporaryFile tf; + if (!tf.open()) + return msgCannotCreateTempFile(tf.errorString()); + stubFifoDir = tf.fileName(); + } + // By now the temp file was deleted again + d->m_stubServerDir = QFile::encodeName(stubFifoDir); + if (!::mkdir(d->m_stubServerDir.constData(), 0700)) + break; + if (errno != EEXIST) + return msgCannotCreateTempDir(stubFifoDir, QString::fromLocal8Bit(strerror(errno))); + } + const QString stubServer = stubFifoDir + QLatin1String("/stub-socket"); + if (!d->m_stubServer.listen(stubServer)) { + ::rmdir(d->m_stubServerDir.constData()); + return tr("Cannot create socket \"%1\": %2").arg(stubServer, d->m_stubServer.errorString()); + } + return QString(); +#endif +} + +void ConsoleProcess::stubServerShutdown() +{ +#ifdef Q_OS_WIN + delete d->m_stubSocket; + d->m_stubSocket = nullptr; + if (d->m_stubServer.isListening()) + d->m_stubServer.close(); +#else + if (d->m_stubSocket) { + readStubOutput(); // we could get the shutdown signal before emptying the buffer + d->m_stubSocket->disconnect(); // avoid getting queued readyRead signals + d->m_stubSocket->deleteLater(); // we might be called from the disconnected signal of m_stubSocket + } + d->m_stubSocket = nullptr; + if (d->m_stubServer.isListening()) { + d->m_stubServer.close(); + ::rmdir(d->m_stubServerDir.constData()); + } +#endif +} + +void ConsoleProcess::stubConnectionAvailable() +{ + d->m_stubConnected = true; + emit stubStarted(); + + if (d->m_stubConnectTimer) { + delete d->m_stubConnectTimer; + d->m_stubConnectTimer = nullptr; + } + + d->m_stubSocket = d->m_stubServer.nextPendingConnection(); + connect(d->m_stubSocket, &QIODevice::readyRead, this, &ConsoleProcess::readStubOutput); + + if (HostOsInfo::isAnyUnixHost()) + connect(d->m_stubSocket, &QLocalSocket::disconnected, this, &ConsoleProcess::stubExited); +} + +static QString errorMsg(int code) +{ + return QString::fromLocal8Bit(strerror(code)); +} + +void ConsoleProcess::readStubOutput() +{ + while (d->m_stubSocket->canReadLine()) { + QByteArray out = d->m_stubSocket->readLine(); +#ifdef Q_OS_WIN + out.chop(2); // \r\n + if (out.startsWith("err:chdir ")) { + emitError(QProcess::FailedToStart, msgCannotChangeToWorkDir(workingDirectory(), winErrorMessage(out.mid(10).toInt()))); + } else if (out.startsWith("err:exec ")) { + emitError(QProcess::FailedToStart, msgCannotExecute(d->m_commandLine.executable().toUserOutput(), winErrorMessage(out.mid(9).toInt()))); + } else if (out.startsWith("thread ")) { // Windows only + d->m_appMainThreadId = out.mid(7).toLongLong(); + } else if (out.startsWith("pid ")) { + // Will not need it any more + delete d->m_tempFile; + d->m_tempFile = nullptr; + d->m_appPid = out.mid(4).toLongLong(); + + d->m_hInferior = OpenProcess( + SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, + FALSE, d->m_appPid); + if (d->m_hInferior == NULL) { + emitError(QProcess::FailedToStart, tr("Cannot obtain a handle to the inferior: %1") + .arg(winErrorMessage(GetLastError()))); + // Uhm, and now what? + continue; + } + d->inferiorFinishedNotifier = new QWinEventNotifier(d->m_hInferior, this); + connect(d->inferiorFinishedNotifier, &QWinEventNotifier::activated, this, [this] { + DWORD chldStatus; + + if (!GetExitCodeProcess(d->m_hInferior, &chldStatus)) + emitError(QProcess::UnknownError, tr("Cannot obtain exit status from inferior: %1") + .arg(winErrorMessage(GetLastError()))); + cleanupInferior(); + d->m_appStatus = QProcess::NormalExit; + d->m_appCode = chldStatus; + emit processStopped(d->m_appCode, d->m_appStatus); + }); + + emit processStarted(); + } else { + emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); + TerminateProcess(d->m_pid->hProcess, (unsigned)-1); + break; + } +#else + out.chop(1); // \n + if (out.startsWith("err:chdir ")) { + emitError(QProcess::FailedToStart, msgCannotChangeToWorkDir(workingDirectory(), errorMsg(out.mid(10).toInt()))); + } else if (out.startsWith("err:exec ")) { + emitError(QProcess::FailedToStart, msgCannotExecute(d->m_commandLine.executable().toString(), errorMsg(out.mid(9).toInt()))); + } else if (out.startsWith("spid ")) { + delete d->m_tempFile; + d->m_tempFile = nullptr; + + d->m_stubPid = out.mid(4).toInt(); + } else if (out.startsWith("pid ")) { + d->m_appPid = out.mid(4).toInt(); + emit processStarted(); + } else if (out.startsWith("exit ")) { + d->m_appStatus = QProcess::NormalExit; + d->m_appCode = out.mid(5).toInt(); + d->m_appPid = 0; + emit processStopped(d->m_appCode, d->m_appStatus); + } else if (out.startsWith("crash ")) { + d->m_appStatus = QProcess::CrashExit; + d->m_appCode = out.mid(6).toInt(); + d->m_appPid = 0; + emit processStopped(d->m_appCode, d->m_appStatus); + } else { + emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); + d->m_stubPid = 0; + d->m_process.terminate(); + break; + } +#endif + } // while +} + +void ConsoleProcess::stubExited() +{ + // The stub exit might get noticed before we read the pid for the kill on Windows + // or the error status elsewhere. + if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState) + d->m_stubSocket->waitForDisconnected(); + +#ifdef Q_OS_WIN + cleanupStub(); + if (d->m_hInferior != NULL) { + TerminateProcess(d->m_hInferior, (unsigned)-1); + cleanupInferior(); + d->m_appStatus = QProcess::CrashExit; + d->m_appCode = -1; + emit processStopped(d->m_appCode, d->m_appStatus); + } +#else + stubServerShutdown(); + d->m_stubPid = 0; + delete d->m_tempFile; + d->m_tempFile = nullptr; + if (d->m_appPid) { + d->m_appStatus = QProcess::CrashExit; + d->m_appCode = -1; + d->m_appPid = 0; + emit processStopped(d->m_appCode, d->m_appStatus); // Maybe it actually did not, but keep state consistent + } +#endif + emit stubStopped(); +} + +void ConsoleProcess::detachStub() +{ + if (HostOsInfo::isAnyUnixHost()) { + if (d->m_stubSocket && d->m_stubSocket->isWritable()) { + d->m_stubSocket->write("d", 1); + d->m_stubSocket->flush(); + } + stubServerShutdown(); + d->m_stubPid = 0; + } +} + +void ConsoleProcess::cleanupInferior() +{ +#ifdef Q_OS_WIN + delete d->inferiorFinishedNotifier; + d->inferiorFinishedNotifier = nullptr; + CloseHandle(d->m_hInferior); + d->m_hInferior = NULL; + d->m_appPid = 0; +#endif +} + +void ConsoleProcess::cleanupStub() +{ +#ifdef Q_OS_WIN + stubServerShutdown(); + delete d->processFinishedNotifier; + d->processFinishedNotifier = nullptr; + CloseHandle(d->m_pid->hThread); + CloseHandle(d->m_pid->hProcess); + delete d->m_pid; + d->m_pid = nullptr; + delete d->m_tempFile; + d->m_tempFile = nullptr; +#endif +} + void ConsoleProcess::setMode(Mode m) { d->m_mode = m; @@ -174,4 +1009,4 @@ bool TerminalCommand::operator<(const TerminalCommand &other) const return command < other.command; } -} +} // Utils |