/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** No Commercial Usage ** ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "consoleprocess.h" #include "environment.h" #include "qtcprocess.h" #include "winutils.h" #include #include #include #include #include #include #include #include #include namespace Utils { struct ConsoleProcessPrivate { ConsoleProcessPrivate(); ConsoleProcess::Mode m_mode; qint64 m_appPid; qint64 m_appMainThreadId; int m_appCode; QString m_executable; QProcess::ExitStatus m_appStatus; QLocalServer m_stubServer; QLocalSocket *m_stubSocket; QTemporaryFile *m_tempFile; PROCESS_INFORMATION *m_pid; HANDLE m_hInferior; QWinEventNotifier *inferiorFinishedNotifier; QWinEventNotifier *processFinishedNotifier; }; ConsoleProcessPrivate::ConsoleProcessPrivate() : m_mode(ConsoleProcess::Run), m_appPid(0),m_appMainThreadId(0), m_stubSocket(0), m_tempFile(0), m_pid(0), m_hInferior(NULL), inferiorFinishedNotifier(0), processFinishedNotifier(0) { } ConsoleProcess::ConsoleProcess(QObject *parent) : QObject(parent), d(new ConsoleProcessPrivate) { connect(&d->m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable())); } ConsoleProcess::~ConsoleProcess() { stop(); } void ConsoleProcess::setMode(Mode m) { d->m_mode = m; } ConsoleProcess::Mode ConsoleProcess::mode() const { return d->m_mode; } qint64 ConsoleProcess::applicationPID() const { return d->m_appPid; } qint64 ConsoleProcess::applicationMainThreadID() const { return d->m_appMainThreadId; } int ConsoleProcess::exitCode() const { return d->m_appCode; } // This will be the signal number if exitStatus == CrashExit QProcess::ExitStatus ConsoleProcess::exitStatus() const { return d->m_appStatus; } bool ConsoleProcess::start(const QString &program, const QString &args) { if (isRunning()) return false; QString pcmd; QString pargs; if (d->m_mode != Run) { // The debugger engines already pre-process the arguments. pcmd = program; pargs = args; } else { QtcProcess::prepareCommand(program, args, &pcmd, &pargs, &m_environment, &m_workingDir); } const QString err = stubServerListen(); if (!err.isEmpty()) { emit processMessage(msgCommChannelFailed(err), true); return false; } QStringList env = m_environment.toStringList(); if (!env.isEmpty()) { d->m_tempFile = new QTemporaryFile(); if (!d->m_tempFile->open()) { stubServerShutdown(); emit processMessage(msgCannotCreateTempFile(d->m_tempFile->errorString()), true); delete d->m_tempFile; d->m_tempFile = 0; return false; } QTextStream out(d->m_tempFile); out.setCodec("UTF-16LE"); out.setGenerateByteOrderMark(false); foreach (const QString &var, fixWinEnvironment(env)) out << var << QChar(0); out << QChar(0); } 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('\\')) workDir.append('\\'); QStringList stubArgs; stubArgs << modeOption(d->m_mode) << d->m_stubServer.fullServerName() << workDir << (d->m_tempFile ? d->m_tempFile->fileName() : 0) << 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 = 0; delete d->m_tempFile; d->m_tempFile = 0; stubServerShutdown(); emit processMessage(tr("The process '%1' could not be started: %2").arg(cmdLine, winErrorMessage(GetLastError())), true); return false; } d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this); connect(d->processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited())); emit wrapperStarted(); return true; } void ConsoleProcess::stop() { if (d->m_hInferior != NULL) { TerminateProcess(d->m_hInferior, (unsigned)-1); cleanupInferior(); } if (d->m_pid) { TerminateProcess(d->m_pid->hProcess, (unsigned)-1); WaitForSingleObject(d->m_pid->hProcess, INFINITE); cleanupStub(); } } bool ConsoleProcess::isRunning() const { return d->m_pid != 0; } QString ConsoleProcess::stubServerListen() { if (d->m_stubServer.listen(QString::fromLatin1("creator-%1-%2") .arg(QCoreApplication::applicationPid()) .arg(rand()))) return QString(); return d->m_stubServer.errorString(); } void ConsoleProcess::stubServerShutdown() { delete d->m_stubSocket; d->m_stubSocket = 0; if (d->m_stubServer.isListening()) d->m_stubServer.close(); } void ConsoleProcess::stubConnectionAvailable() { d->m_stubSocket = d->m_stubServer.nextPendingConnection(); connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput())); } void ConsoleProcess::readStubOutput() { while (d->m_stubSocket->canReadLine()) { QByteArray out = d->m_stubSocket->readLine(); out.chop(2); // \r\n if (out.startsWith("err:chdir ")) { emit processMessage(msgCannotChangeToWorkDir(workingDirectory(), winErrorMessage(out.mid(10).toInt())), true); } else if (out.startsWith("err:exec ")) { emit processMessage(msgCannotExecute(d->m_executable, winErrorMessage(out.mid(9).toInt())), true); } 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 = 0; 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) { emit processMessage(tr("Cannot obtain a handle to the inferior: %1") .arg(winErrorMessage(GetLastError())), true); // Uhm, and now what? continue; } d->inferiorFinishedNotifier = new QWinEventNotifier(d->m_hInferior, this); connect(d->inferiorFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(inferiorExited())); emit processStarted(); } else { emit processMessage(msgUnexpectedOutput(out), true); TerminateProcess(d->m_pid->hProcess, (unsigned)-1); break; } } } void ConsoleProcess::cleanupInferior() { delete d->inferiorFinishedNotifier; d->inferiorFinishedNotifier = 0; CloseHandle(d->m_hInferior); d->m_hInferior = NULL; d->m_appPid = 0; } void ConsoleProcess::inferiorExited() { DWORD chldStatus; if (!GetExitCodeProcess(d->m_hInferior, &chldStatus)) emit processMessage(tr("Cannot obtain exit status from inferior: %1") .arg(winErrorMessage(GetLastError())), true); cleanupInferior(); d->m_appStatus = QProcess::NormalExit; d->m_appCode = chldStatus; emit processStopped(); } void ConsoleProcess::cleanupStub() { stubServerShutdown(); delete d->processFinishedNotifier; d->processFinishedNotifier = 0; CloseHandle(d->m_pid->hThread); CloseHandle(d->m_pid->hProcess); delete d->m_pid; d->m_pid = 0; delete d->m_tempFile; d->m_tempFile = 0; } void ConsoleProcess::stubExited() { // The stub exit might get noticed before we read the pid for the kill. if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState) d->m_stubSocket->waitForDisconnected(); cleanupStub(); if (d->m_hInferior != NULL) { TerminateProcess(d->m_hInferior, (unsigned)-1); cleanupInferior(); d->m_appStatus = QProcess::CrashExit; d->m_appCode = -1; emit processStopped(); } emit wrapperStopped(); } } // namespace Utils