You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
7.9 KiB
258 lines
7.9 KiB
/** |
|
****************************************************************************** |
|
* |
|
* @file consoleprocess_unix.cpp |
|
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. |
|
* Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009. |
|
* @brief |
|
* @see The GNU Public License (GPL) Version 3 |
|
* @defgroup |
|
* @{ |
|
* |
|
*****************************************************************************/ |
|
/* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License along |
|
* with this program; if not, write to the Free Software Foundation, Inc., |
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
*/ |
|
|
|
#include "consoleprocess.h" |
|
|
|
#include <QtCore/QCoreApplication> |
|
#include <QtCore/QDir> |
|
#include <QtCore/QSettings> |
|
#include <QtCore/QTemporaryFile> |
|
|
|
#include <QtNetwork/QLocalSocket> |
|
|
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <errno.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
|
|
using namespace Utils; |
|
|
|
ConsoleProcess::ConsoleProcess(QObject *parent) : |
|
QObject(parent), |
|
m_mode(Run), |
|
m_appPid(0), |
|
m_stubSocket(0), |
|
m_settings(0) |
|
{ |
|
connect(&m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable())); |
|
|
|
m_process.setProcessChannelMode(QProcess::ForwardedChannels); |
|
connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), |
|
SLOT(stubExited())); |
|
} |
|
|
|
ConsoleProcess::~ConsoleProcess() |
|
{ |
|
stop(); |
|
} |
|
|
|
bool ConsoleProcess::start(const QString &program, const QStringList &args) |
|
{ |
|
if (isRunning()) |
|
return false; |
|
|
|
const QString err = stubServerListen(); |
|
if (!err.isEmpty()) { |
|
emit processError(msgCommChannelFailed(err)); |
|
return false; |
|
} |
|
|
|
if (!environment().isEmpty()) { |
|
m_tempFile = new QTemporaryFile(); |
|
if (!m_tempFile->open()) { |
|
stubServerShutdown(); |
|
emit processError(msgCannotCreateTempFile(m_tempFile->errorString())); |
|
delete m_tempFile; |
|
m_tempFile = 0; |
|
return false; |
|
} |
|
foreach (const QString &var, environment()) { |
|
m_tempFile->write(var.toLocal8Bit()); |
|
m_tempFile->write("", 1); |
|
} |
|
m_tempFile->flush(); |
|
} |
|
|
|
QStringList xtermArgs = terminalEmulator(m_settings).split(QLatin1Char(' ')); // FIXME: quoting |
|
xtermArgs |
|
#ifdef Q_OS_MAC |
|
<< (QCoreApplication::applicationDirPath() + QLatin1String("/../Resources/qtcreator_process_stub")) |
|
#else |
|
<< (QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_process_stub")) |
|
#endif |
|
<< modeOption(m_mode) |
|
<< m_stubServer.fullServerName() |
|
<< msgPromptToClose() |
|
<< workingDirectory() |
|
<< (m_tempFile ? m_tempFile->fileName() : 0) |
|
<< program << args; |
|
|
|
QString xterm = xtermArgs.takeFirst(); |
|
m_process.start(xterm, xtermArgs); |
|
if (!m_process.waitForStarted()) { |
|
stubServerShutdown(); |
|
emit processError(tr("Cannot start the terminal emulator '%1'.").arg(xterm)); |
|
delete m_tempFile; |
|
m_tempFile = 0; |
|
return false; |
|
} |
|
m_executable = program; |
|
emit wrapperStarted(); |
|
return true; |
|
} |
|
|
|
void ConsoleProcess::stop() |
|
{ |
|
if (!isRunning()) |
|
return; |
|
stubServerShutdown(); |
|
m_appPid = 0; |
|
m_process.terminate(); |
|
if (!m_process.waitForFinished(1000)) |
|
m_process.kill(); |
|
m_process.waitForFinished(); |
|
} |
|
|
|
bool ConsoleProcess::isRunning() const |
|
{ |
|
return m_process.state() != QProcess::NotRunning; |
|
} |
|
|
|
QString ConsoleProcess::stubServerListen() |
|
{ |
|
// We need to put the socket in a private directory, as some systems simply do not |
|
// check the file permissions of sockets. |
|
QString stubFifoDir; |
|
forever { |
|
{ |
|
QTemporaryFile tf; |
|
if (!tf.open()) |
|
return msgCannotCreateTempFile(tf.errorString()); |
|
stubFifoDir = QFile::encodeName(tf.fileName()); |
|
} |
|
// By now the temp file was deleted again |
|
m_stubServerDir = QFile::encodeName(stubFifoDir); |
|
if (!::mkdir(m_stubServerDir.constData(), 0700)) |
|
break; |
|
if (errno != EEXIST) |
|
return msgCannotCreateTempDir(stubFifoDir, QString::fromLocal8Bit(strerror(errno))); |
|
} |
|
const QString stubServer = stubFifoDir + "/stub-socket"; |
|
if (!m_stubServer.listen(stubServer)) { |
|
::rmdir(m_stubServerDir.constData()); |
|
return tr("Cannot create socket '%1': %2").arg(stubServer, m_stubServer.errorString()); |
|
} |
|
return QString(); |
|
} |
|
|
|
void ConsoleProcess::stubServerShutdown() |
|
{ |
|
delete m_stubSocket; |
|
m_stubSocket = 0; |
|
if (m_stubServer.isListening()) { |
|
m_stubServer.close(); |
|
::rmdir(m_stubServerDir.constData()); |
|
} |
|
} |
|
|
|
void ConsoleProcess::stubConnectionAvailable() |
|
{ |
|
m_stubSocket = m_stubServer.nextPendingConnection(); |
|
connect(m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput())); |
|
} |
|
|
|
static QString errorMsg(int code) |
|
{ |
|
return QString::fromLocal8Bit(strerror(code)); |
|
} |
|
|
|
void ConsoleProcess::readStubOutput() |
|
{ |
|
while (m_stubSocket->canReadLine()) { |
|
QByteArray out = m_stubSocket->readLine(); |
|
out.chop(1); // \n |
|
if (out.startsWith("err:chdir ")) { |
|
emit processError(msgCannotChangeToWorkDir(workingDirectory(), errorMsg(out.mid(10).toInt()))); |
|
} else if (out.startsWith("err:exec ")) { |
|
emit processError(msgCannotExecute(m_executable, errorMsg(out.mid(9).toInt()))); |
|
} else if (out.startsWith("pid ")) { |
|
// Will not need it any more |
|
delete m_tempFile; |
|
m_tempFile = 0; |
|
|
|
m_appPid = out.mid(4).toInt(); |
|
emit processStarted(); |
|
} else if (out.startsWith("exit ")) { |
|
m_appStatus = QProcess::NormalExit; |
|
m_appCode = out.mid(5).toInt(); |
|
m_appPid = 0; |
|
emit processStopped(); |
|
} else if (out.startsWith("crash ")) { |
|
m_appStatus = QProcess::CrashExit; |
|
m_appCode = out.mid(6).toInt(); |
|
m_appPid = 0; |
|
emit processStopped(); |
|
} else { |
|
emit processError(msgUnexpectedOutput()); |
|
m_process.terminate(); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
void ConsoleProcess::stubExited() |
|
{ |
|
// The stub exit might get noticed before we read the error status. |
|
if (m_stubSocket && m_stubSocket->state() == QLocalSocket::ConnectedState) |
|
m_stubSocket->waitForDisconnected(); |
|
stubServerShutdown(); |
|
delete m_tempFile; |
|
m_tempFile = 0; |
|
if (m_appPid) { |
|
m_appStatus = QProcess::CrashExit; |
|
m_appCode = -1; |
|
m_appPid = 0; |
|
emit processStopped(); // Maybe it actually did not, but keep state consistent |
|
} |
|
emit wrapperStopped(); |
|
} |
|
|
|
QString ConsoleProcess::defaultTerminalEmulator() |
|
{ |
|
// FIXME: enable this once runInTerminal works nicely |
|
#if 0 //def Q_OS_MAC |
|
return QDir::cleanPath(QCoreApplication::applicationDirPath() |
|
+ QLatin1String("/../Resources/runInTerminal.command")); |
|
#else |
|
return QLatin1String("xterm"); |
|
#endif |
|
} |
|
|
|
QString ConsoleProcess::terminalEmulator(const QSettings *settings) |
|
{ |
|
const QString dflt = defaultTerminalEmulator() + QLatin1String(" -e"); |
|
if (!settings) |
|
return dflt; |
|
return settings->value(QLatin1String("General/TerminalEmulator"), dflt).toString(); |
|
} |
|
|
|
void ConsoleProcess::setTerminalEmulator(QSettings *settings, const QString &term) |
|
{ |
|
return settings->setValue(QLatin1String("General/TerminalEmulator"), term); |
|
}
|
|
|