forked from qt-creator/qt-creator
consoleprocess: support Terminal.app on mac
Distinguishes the process that starts the terminal from the stub process, as on mac to support Terminal.app they are different. Handle the stub not through the process that starts the terminal, but through the local socket (on *nix). Replace the blocking wait(...) in the main thread, with a nonblocking wait in the signal handler when receiving a SIGCHLD, to leave the main thread able to handle communication with creator. This change allows the use of terminal emulator commands that share a single instance or that fork. So this is also the real fix for QTCREATORBUG-1633 on linux. If creator crashes the stub and the debugged program live on. This was done on purpose, it could be changed if considered better. Task-number: QTCREATORBUG-6371 Task-number: QTCREATORBUG-1633 Change-Id: I4d4fb3a67b1987f4e46e2c603dcefe8c15152ad2 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
This commit is contained in:
@@ -1,11 +1,18 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
i=`pwd`
|
# ugly escaping: for apple script \ and " need to be escaped, whereas %q takes care of all bash escaping
|
||||||
i=${i//\\/\\\\\\\\}
|
declare -a args
|
||||||
i=${i//\"/\\\\\\\"}
|
mydir=`pwd`
|
||||||
i=${i//\$/\\\\\\\$}
|
mydir=$(printf '%q' "$mydir")
|
||||||
i=${i//\`/\\\\\\\`}
|
mydir="${mydir//\\/\\\\}"
|
||||||
i=\\\"$i\\\"
|
args[0]="cd ${mydir//\"/\\\"};"
|
||||||
|
for a in "$@" ; do
|
||||||
|
x=$(printf '%q ' "$a")
|
||||||
|
x="${x//\\/\\\\}"
|
||||||
|
args[${#args[@]}]="${x//\"/\\\"}"
|
||||||
|
done
|
||||||
|
mArgs=${args[@]:0}
|
||||||
|
|
||||||
osascript <<EOF
|
osascript <<EOF
|
||||||
--Terminal opens a window by default when it is not running, so check
|
--Terminal opens a window by default when it is not running, so check
|
||||||
on applicationIsRunning(applicationName)
|
on applicationIsRunning(applicationName)
|
||||||
@@ -14,7 +21,7 @@ osascript <<EOF
|
|||||||
end applicationIsRunning
|
end applicationIsRunning
|
||||||
set terminalWasRunning to applicationIsRunning("Terminal")
|
set terminalWasRunning to applicationIsRunning("Terminal")
|
||||||
|
|
||||||
set cdScript to "cd $i"
|
set cdScript to "$mArgs"
|
||||||
tell application "Terminal"
|
tell application "Terminal"
|
||||||
--do script will open a new window if none given, but terminal already opens one if not running
|
--do script will open a new window if none given, but terminal already opens one if not running
|
||||||
if terminalWasRunning then
|
if terminalWasRunning then
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
#include "consoleprocess_p.h"
|
#include "consoleprocess_p.h"
|
||||||
|
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|||||||
@@ -62,16 +62,23 @@ public:
|
|||||||
Environment environment() const;
|
Environment environment() const;
|
||||||
|
|
||||||
bool start(const QString &program, const QString &args);
|
bool start(const QString &program, const QString &args);
|
||||||
|
public slots:
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
public:
|
||||||
void setMode(Mode m);
|
void setMode(Mode m);
|
||||||
Mode mode() const;
|
Mode mode() const;
|
||||||
|
|
||||||
bool isRunning() const; // This reflects the state of the console+stub
|
bool isRunning() const; // This reflects the state of the console+stub
|
||||||
qint64 applicationPID() const;
|
qint64 applicationPID() const;
|
||||||
|
|
||||||
|
void killProcess();
|
||||||
|
void killStub();
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
qint64 applicationMainThreadID() const;
|
qint64 applicationMainThreadID() const;
|
||||||
|
#else
|
||||||
|
void detachStub();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int exitCode() const;
|
int exitCode() const;
|
||||||
@@ -99,8 +106,8 @@ signals:
|
|||||||
void processStopped();
|
void processStopped();
|
||||||
|
|
||||||
// These reflect the state of the console+stub
|
// These reflect the state of the console+stub
|
||||||
void wrapperStarted();
|
void stubStarted();
|
||||||
void wrapperStopped();
|
void stubStopped();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void stubConnectionAvailable();
|
void stubConnectionAvailable();
|
||||||
|
|||||||
@@ -37,6 +37,10 @@
|
|||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QTimer;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
# if QT_VERSION >= 0x050000
|
# if QT_VERSION >= 0x050000
|
||||||
# include <QWinEventNotifier>
|
# include <QWinEventNotifier>
|
||||||
@@ -51,6 +55,7 @@ namespace Utils {
|
|||||||
struct ConsoleProcessPrivate {
|
struct ConsoleProcessPrivate {
|
||||||
ConsoleProcessPrivate();
|
ConsoleProcessPrivate();
|
||||||
|
|
||||||
|
static QString m_defaultConsoleProcess;
|
||||||
ConsoleProcess::Mode m_mode;
|
ConsoleProcess::Mode m_mode;
|
||||||
QString m_workingDir;
|
QString m_workingDir;
|
||||||
Environment m_environment;
|
Environment m_environment;
|
||||||
@@ -66,6 +71,9 @@ struct ConsoleProcessPrivate {
|
|||||||
QProcess m_process;
|
QProcess m_process;
|
||||||
QByteArray m_stubServerDir;
|
QByteArray m_stubServerDir;
|
||||||
QSettings *m_settings;
|
QSettings *m_settings;
|
||||||
|
bool m_stubConnected;
|
||||||
|
qint64 m_stubPid;
|
||||||
|
QTimer *m_stubConnectTimer;
|
||||||
#else
|
#else
|
||||||
qint64 m_appMainThreadId;
|
qint64 m_appMainThreadId;
|
||||||
PROCESS_INFORMATION *m_pid;
|
PROCESS_INFORMATION *m_pid;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -51,7 +52,10 @@ ConsoleProcessPrivate::ConsoleProcessPrivate() :
|
|||||||
m_appPid(0),
|
m_appPid(0),
|
||||||
m_stubSocket(0),
|
m_stubSocket(0),
|
||||||
m_tempFile(0),
|
m_tempFile(0),
|
||||||
m_settings(0)
|
m_settings(0),
|
||||||
|
m_stubConnected(false),
|
||||||
|
m_stubPid(0),
|
||||||
|
m_stubConnectTimer(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +65,6 @@ ConsoleProcess::ConsoleProcess(QObject *parent) :
|
|||||||
connect(&d->m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable()));
|
connect(&d->m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable()));
|
||||||
|
|
||||||
d->m_process.setProcessChannelMode(QProcess::ForwardedChannels);
|
d->m_process.setProcessChannelMode(QProcess::ForwardedChannels);
|
||||||
connect(&d->m_process, SIGNAL(finished(int,QProcess::ExitStatus)),
|
|
||||||
SLOT(stubExited()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleProcess::setSettings(QSettings *settings)
|
void ConsoleProcess::setSettings(QSettings *settings)
|
||||||
@@ -159,21 +161,54 @@ bool ConsoleProcess::start(const QString &program, const QString &args)
|
|||||||
d->m_tempFile = 0;
|
d->m_tempFile = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
d->m_stubConnectTimer = new QTimer(this);
|
||||||
|
connect(d->m_stubConnectTimer, SIGNAL(timeout()), SLOT(stop()));
|
||||||
|
d->m_stubConnectTimer->setSingleShot(true);
|
||||||
|
d->m_stubConnectTimer->start(10000);
|
||||||
d->m_executable = program;
|
d->m_executable = program;
|
||||||
emit wrapperStarted();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConsoleProcess::killProcess()
|
||||||
|
{
|
||||||
|
if (d->m_stubSocket && d->m_stubSocket->isWritable()) {
|
||||||
|
d->m_stubSocket->write("k", 1);
|
||||||
|
d->m_stubSocket->flush();
|
||||||
|
}
|
||||||
|
d->m_appPid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleProcess::killStub()
|
||||||
|
{
|
||||||
|
if (d->m_stubSocket && d->m_stubSocket->isWritable()) {
|
||||||
|
d->m_stubSocket->write("s", 1);
|
||||||
|
d->m_stubSocket->flush();
|
||||||
|
}
|
||||||
|
stubServerShutdown();
|
||||||
|
d->m_stubPid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleProcess::detachStub()
|
||||||
|
{
|
||||||
|
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::stop()
|
void ConsoleProcess::stop()
|
||||||
{
|
{
|
||||||
if (!isRunning())
|
killProcess();
|
||||||
return;
|
killStub();
|
||||||
stubServerShutdown();
|
if (isRunning()) {
|
||||||
d->m_appPid = 0;
|
d->m_process.terminate();
|
||||||
d->m_process.terminate();
|
if (!d->m_process.waitForFinished(1000)) {
|
||||||
if (!d->m_process.waitForFinished(1000))
|
d->m_process.kill();
|
||||||
d->m_process.kill();
|
d->m_process.waitForFinished();
|
||||||
d->m_process.waitForFinished();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConsoleProcess::isRunning() const
|
bool ConsoleProcess::isRunning() const
|
||||||
@@ -210,7 +245,8 @@ QString ConsoleProcess::stubServerListen()
|
|||||||
|
|
||||||
void ConsoleProcess::stubServerShutdown()
|
void ConsoleProcess::stubServerShutdown()
|
||||||
{
|
{
|
||||||
delete d->m_stubSocket;
|
if (d->m_stubSocket)
|
||||||
|
d->m_stubSocket->deleteLater(); // we might be called from the disconnected signal of m_stubSocket
|
||||||
d->m_stubSocket = 0;
|
d->m_stubSocket = 0;
|
||||||
if (d->m_stubServer.isListening()) {
|
if (d->m_stubServer.isListening()) {
|
||||||
d->m_stubServer.close();
|
d->m_stubServer.close();
|
||||||
@@ -220,8 +256,15 @@ void ConsoleProcess::stubServerShutdown()
|
|||||||
|
|
||||||
void ConsoleProcess::stubConnectionAvailable()
|
void ConsoleProcess::stubConnectionAvailable()
|
||||||
{
|
{
|
||||||
|
if (d->m_stubConnectTimer) {
|
||||||
|
delete d->m_stubConnectTimer;
|
||||||
|
d->m_stubConnectTimer = 0;
|
||||||
|
}
|
||||||
|
d->m_stubConnected = true;
|
||||||
|
emit stubStarted();
|
||||||
d->m_stubSocket = d->m_stubServer.nextPendingConnection();
|
d->m_stubSocket = d->m_stubServer.nextPendingConnection();
|
||||||
connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput()));
|
connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput()));
|
||||||
|
connect(d->m_stubSocket, SIGNAL(disconnected()), SLOT(stubExited()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString errorMsg(int code)
|
static QString errorMsg(int code)
|
||||||
@@ -238,11 +281,12 @@ void ConsoleProcess::readStubOutput()
|
|||||||
emit processError(msgCannotChangeToWorkDir(workingDirectory(), errorMsg(out.mid(10).toInt())));
|
emit processError(msgCannotChangeToWorkDir(workingDirectory(), errorMsg(out.mid(10).toInt())));
|
||||||
} else if (out.startsWith("err:exec ")) {
|
} else if (out.startsWith("err:exec ")) {
|
||||||
emit processError(msgCannotExecute(d->m_executable, errorMsg(out.mid(9).toInt())));
|
emit processError(msgCannotExecute(d->m_executable, errorMsg(out.mid(9).toInt())));
|
||||||
} else if (out.startsWith("pid ")) {
|
} else if (out.startsWith("spid ")) {
|
||||||
// Will not need it any more
|
|
||||||
delete d->m_tempFile;
|
delete d->m_tempFile;
|
||||||
d->m_tempFile = 0;
|
d->m_tempFile = 0;
|
||||||
|
|
||||||
|
d->m_stubPid = out.mid(4).toInt();
|
||||||
|
} else if (out.startsWith("pid ")) {
|
||||||
d->m_appPid = out.mid(4).toInt();
|
d->m_appPid = out.mid(4).toInt();
|
||||||
emit processStarted();
|
emit processStarted();
|
||||||
} else if (out.startsWith("exit ")) {
|
} else if (out.startsWith("exit ")) {
|
||||||
@@ -257,6 +301,7 @@ void ConsoleProcess::readStubOutput()
|
|||||||
emit processStopped();
|
emit processStopped();
|
||||||
} else {
|
} else {
|
||||||
emit processError(msgUnexpectedOutput(out));
|
emit processError(msgUnexpectedOutput(out));
|
||||||
|
d->m_stubPid = 0;
|
||||||
d->m_process.terminate();
|
d->m_process.terminate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -269,6 +314,7 @@ void ConsoleProcess::stubExited()
|
|||||||
if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState)
|
if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState)
|
||||||
d->m_stubSocket->waitForDisconnected();
|
d->m_stubSocket->waitForDisconnected();
|
||||||
stubServerShutdown();
|
stubServerShutdown();
|
||||||
|
d->m_stubPid = 0;
|
||||||
delete d->m_tempFile;
|
delete d->m_tempFile;
|
||||||
d->m_tempFile = 0;
|
d->m_tempFile = 0;
|
||||||
if (d->m_appPid) {
|
if (d->m_appPid) {
|
||||||
@@ -277,7 +323,7 @@ void ConsoleProcess::stubExited()
|
|||||||
d->m_appPid = 0;
|
d->m_appPid = 0;
|
||||||
emit processStopped(); // Maybe it actually did not, but keep state consistent
|
emit processStopped(); // Maybe it actually did not, but keep state consistent
|
||||||
}
|
}
|
||||||
emit wrapperStopped();
|
emit stubStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Terminal {
|
struct Terminal {
|
||||||
@@ -293,15 +339,18 @@ static const Terminal knownTerminals[] =
|
|||||||
{"rxvt", "-e"},
|
{"rxvt", "-e"},
|
||||||
{"urxvt", "-e"},
|
{"urxvt", "-e"},
|
||||||
{"xfce4-terminal", "-x"},
|
{"xfce4-terminal", "-x"},
|
||||||
{"konsole", "--nofork -e"},
|
{"konsole", "-e"},
|
||||||
{"gnome-terminal", "-x"}
|
{"gnome-terminal", "-x"}
|
||||||
};
|
};
|
||||||
|
|
||||||
QString ConsoleProcess::defaultTerminalEmulator()
|
QString ConsoleProcess::defaultTerminalEmulator()
|
||||||
{
|
{
|
||||||
if (Utils::HostOsInfo::isMacHost())
|
if (Utils::HostOsInfo::isMacHost()) {
|
||||||
|
QString termCmd = QCoreApplication::applicationDirPath() + QLatin1String("/../Resources/scripts/openTerminal.command");
|
||||||
|
if (QFile(termCmd).exists())
|
||||||
|
return termCmd.replace(QLatin1Char(' '), QLatin1String("\\ "));
|
||||||
return QLatin1String("/usr/X11/bin/xterm");
|
return QLatin1String("/usr/X11/bin/xterm");
|
||||||
|
}
|
||||||
const Environment env = Environment::systemEnvironment();
|
const Environment env = Environment::systemEnvironment();
|
||||||
const int terminalCount = int(sizeof(knownTerminals) / sizeof(knownTerminals[0]));
|
const int terminalCount = int(sizeof(knownTerminals) / sizeof(knownTerminals[0]));
|
||||||
for (int i = 0; i < terminalCount; ++i) {
|
for (int i = 0; i < terminalCount; ++i) {
|
||||||
@@ -317,9 +366,6 @@ QString ConsoleProcess::defaultTerminalEmulator()
|
|||||||
|
|
||||||
QStringList ConsoleProcess::availableTerminalEmulators()
|
QStringList ConsoleProcess::availableTerminalEmulators()
|
||||||
{
|
{
|
||||||
if (Utils::HostOsInfo::isMacHost())
|
|
||||||
return QStringList(defaultTerminalEmulator());
|
|
||||||
|
|
||||||
QStringList result;
|
QStringList result;
|
||||||
const Environment env = Environment::systemEnvironment();
|
const Environment env = Environment::systemEnvironment();
|
||||||
const int terminalCount = int(sizeof(knownTerminals) / sizeof(knownTerminals[0]));
|
const int terminalCount = int(sizeof(knownTerminals) / sizeof(knownTerminals[0]));
|
||||||
@@ -331,6 +377,8 @@ QStringList ConsoleProcess::availableTerminalEmulators()
|
|||||||
result.push_back(terminal);
|
result.push_back(terminal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!result.contains(defaultTerminalEmulator()))
|
||||||
|
result.append(defaultTerminalEmulator());
|
||||||
result.sort();
|
result.sort();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,16 +151,20 @@ bool ConsoleProcess::start(const QString &program, const QString &args)
|
|||||||
|
|
||||||
d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this);
|
d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this);
|
||||||
connect(d->processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited()));
|
connect(d->processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited()));
|
||||||
emit wrapperStarted();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleProcess::stop()
|
|
||||||
|
void ConsoleProcess::killProcess()
|
||||||
{
|
{
|
||||||
if (d->m_hInferior != NULL) {
|
if (d->m_hInferior != NULL) {
|
||||||
TerminateProcess(d->m_hInferior, (unsigned)-1);
|
TerminateProcess(d->m_hInferior, (unsigned)-1);
|
||||||
cleanupInferior();
|
cleanupInferior();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleProcess::killStub()
|
||||||
|
{
|
||||||
if (d->m_pid) {
|
if (d->m_pid) {
|
||||||
TerminateProcess(d->m_pid->hProcess, (unsigned)-1);
|
TerminateProcess(d->m_pid->hProcess, (unsigned)-1);
|
||||||
WaitForSingleObject(d->m_pid->hProcess, INFINITE);
|
WaitForSingleObject(d->m_pid->hProcess, INFINITE);
|
||||||
@@ -168,6 +172,12 @@ void ConsoleProcess::stop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConsoleProcess::stop()
|
||||||
|
{
|
||||||
|
killProcess();
|
||||||
|
killStub();
|
||||||
|
}
|
||||||
|
|
||||||
bool ConsoleProcess::isRunning() const
|
bool ConsoleProcess::isRunning() const
|
||||||
{
|
{
|
||||||
return d->m_pid != 0;
|
return d->m_pid != 0;
|
||||||
@@ -192,6 +202,7 @@ void ConsoleProcess::stubServerShutdown()
|
|||||||
|
|
||||||
void ConsoleProcess::stubConnectionAvailable()
|
void ConsoleProcess::stubConnectionAvailable()
|
||||||
{
|
{
|
||||||
|
emit stubStarted();
|
||||||
d->m_stubSocket = d->m_stubServer.nextPendingConnection();
|
d->m_stubSocket = d->m_stubServer.nextPendingConnection();
|
||||||
connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput()));
|
connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput()));
|
||||||
}
|
}
|
||||||
@@ -281,7 +292,7 @@ void ConsoleProcess::stubExited()
|
|||||||
d->m_appCode = -1;
|
d->m_appCode = -1;
|
||||||
emit processStopped();
|
emit processStopped();
|
||||||
}
|
}
|
||||||
emit wrapperStopped();
|
emit stubStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList ConsoleProcess::fixWinEnvironment(const QStringList &env)
|
QStringList ConsoleProcess::fixWinEnvironment(const QStringList &env)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
/* For OpenBSD */
|
/* For OpenBSD */
|
||||||
#ifndef EPROTO
|
#ifndef EPROTO
|
||||||
@@ -55,6 +56,10 @@ extern char **environ;
|
|||||||
|
|
||||||
static int qtcFd;
|
static int qtcFd;
|
||||||
static char *sleepMsg;
|
static char *sleepMsg;
|
||||||
|
static int chldPipe[2];
|
||||||
|
static int isDebug;
|
||||||
|
static volatile int isDetached;
|
||||||
|
static volatile int chldPid;
|
||||||
|
|
||||||
static void __attribute__((noreturn)) doExit(int code)
|
static void __attribute__((noreturn)) doExit(int code)
|
||||||
{
|
{
|
||||||
@@ -71,10 +76,10 @@ static void sendMsg(const char *msg, int num)
|
|||||||
char pidStr[64];
|
char pidStr[64];
|
||||||
|
|
||||||
pidStrLen = sprintf(pidStr, msg, num);
|
pidStrLen = sprintf(pidStr, msg, num);
|
||||||
if ((ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) {
|
if (!isDetached && (ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) {
|
||||||
fprintf(stderr, "Cannot write to creator comm socket: %s\n",
|
fprintf(stderr, "Cannot write to creator comm socket: %s\n",
|
||||||
(ioRet < 0) ? strerror(errno) : "short write");
|
(ioRet < 0) ? strerror(errno) : "short write");
|
||||||
doExit(3);
|
isDetached = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,16 +93,88 @@ enum {
|
|||||||
ArgExe
|
ArgExe
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Handle sigchld */
|
||||||
|
static void sigchldHandler(int sig)
|
||||||
|
{
|
||||||
|
int chldStatus;
|
||||||
|
/* Currently we have only one child, so we exit in case of error. */
|
||||||
|
int waitRes;
|
||||||
|
(void)sig;
|
||||||
|
for (;;) {
|
||||||
|
waitRes = waitpid(-1, &chldStatus, WNOHANG);
|
||||||
|
if (!waitRes)
|
||||||
|
break;
|
||||||
|
if (waitRes < 0) {
|
||||||
|
perror("Cannot obtain exit status of child process");
|
||||||
|
doExit(3);
|
||||||
|
}
|
||||||
|
if (WIFSTOPPED(chldStatus)) {
|
||||||
|
/* The child stopped. This can be only the result of ptrace(TRACE_ME). */
|
||||||
|
/* We won't need the notification pipe any more, as we know that
|
||||||
|
* the exec() succeeded. */
|
||||||
|
close(chldPipe[0]);
|
||||||
|
close(chldPipe[1]);
|
||||||
|
chldPipe[0] = -1;
|
||||||
|
/* If we are not debugging, just skip the "handover enabler".
|
||||||
|
* This is suboptimal, as it makes us ignore setuid/-gid bits. */
|
||||||
|
if (isDebug) {
|
||||||
|
/* Stop the child after we detach from it, so we can hand it over to gdb.
|
||||||
|
* If the signal delivery is not queued, things will go awry. It works on
|
||||||
|
* Linux and MacOSX ... */
|
||||||
|
kill(chldPid, SIGSTOP);
|
||||||
|
}
|
||||||
|
#ifdef __linux__
|
||||||
|
ptrace(PTRACE_DETACH, chldPid, 0, 0);
|
||||||
|
#else
|
||||||
|
ptrace(PT_DETACH, chldPid, 0, 0);
|
||||||
|
#endif
|
||||||
|
sendMsg("pid %d\n", chldPid);
|
||||||
|
if (isDetached == 2 && isDebug) {
|
||||||
|
/* qtcreator was not informed and died while debugging, killing the child */
|
||||||
|
kill(chldPid, SIGKILL);
|
||||||
|
}
|
||||||
|
} else if (WIFEXITED(chldStatus)) {
|
||||||
|
int errNo;
|
||||||
|
|
||||||
|
/* The child exited normally. */
|
||||||
|
if (chldPipe[0] >= 0) {
|
||||||
|
/* The child exited before being stopped by ptrace(). That can only
|
||||||
|
* mean that the exec() failed. */
|
||||||
|
switch (read(chldPipe[0], &errNo, sizeof(errNo))) {
|
||||||
|
default:
|
||||||
|
/* Read of unknown length. Should never happen ... */
|
||||||
|
errno = EPROTO;
|
||||||
|
/* fallthrough */
|
||||||
|
case -1:
|
||||||
|
/* Read failed. Should never happen, either ... */
|
||||||
|
perror("Cannot read status from child process");
|
||||||
|
doExit(3);
|
||||||
|
case sizeof(errNo):
|
||||||
|
/* Child telling us the errno from exec(). */
|
||||||
|
sendMsg("err:exec %d\n", errNo);
|
||||||
|
doExit(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMsg("exit %d\n", WEXITSTATUS(chldStatus));
|
||||||
|
doExit(0);
|
||||||
|
} else {
|
||||||
|
sendMsg("crash %d\n", WTERMSIG(chldStatus));
|
||||||
|
doExit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* syntax: $0 {"run"|"debug"} <pid-socket> <continuation-msg> <workdir> <env-file> <exe> <args...> */
|
/* syntax: $0 {"run"|"debug"} <pid-socket> <continuation-msg> <workdir> <env-file> <exe> <args...> */
|
||||||
/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */
|
/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int errNo;
|
int errNo, hadInvalidCommand = 0;
|
||||||
int chldPid;
|
|
||||||
int chldStatus;
|
|
||||||
int chldPipe[2];
|
|
||||||
char **env = 0;
|
char **env = 0;
|
||||||
struct sockaddr_un sau;
|
struct sockaddr_un sau;
|
||||||
|
struct sigaction act;
|
||||||
|
|
||||||
|
memset(&act, 0, sizeof(act));
|
||||||
|
|
||||||
if (argc < ArgEnv) {
|
if (argc < ArgEnv) {
|
||||||
fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n");
|
fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n");
|
||||||
@@ -118,6 +195,11 @@ int main(int argc, char *argv[])
|
|||||||
doExit(1);
|
doExit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDebug = !strcmp(argv[ArgAction], "debug");
|
||||||
|
isDetached = 0;
|
||||||
|
|
||||||
|
sendMsg("spid %ld\n", (long)getpid());
|
||||||
|
|
||||||
if (*argv[ArgDir] && chdir(argv[ArgDir])) {
|
if (*argv[ArgDir] && chdir(argv[ArgDir])) {
|
||||||
/* Only expected error: no such file or direcotry */
|
/* Only expected error: no such file or direcotry */
|
||||||
sendMsg("err:chdir %d\n", errno);
|
sendMsg("err:chdir %d\n", errno);
|
||||||
@@ -154,10 +236,26 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background
|
/*
|
||||||
* process group (in which we will be, once as child and once as parent)
|
* set up the signal handlers
|
||||||
* generates the mentioned signal and stops the concerned process. */
|
*/
|
||||||
signal(SIGTTOU, SIG_IGN);
|
{
|
||||||
|
/* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background
|
||||||
|
* process group (in which we will be, once as child and once as parent)
|
||||||
|
* generates the mentioned signal and stops the concerned process. */
|
||||||
|
act.sa_handler = SIG_IGN;
|
||||||
|
if (sigaction(SIGTTOU, &act, 0)) {
|
||||||
|
perror("sigaction SIGTTOU");
|
||||||
|
doExit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle SIGCHLD to keep track of what the child does without blocking */
|
||||||
|
act.sa_handler = sigchldHandler;
|
||||||
|
if (sigaction(SIGCHLD, &act, 0)) {
|
||||||
|
perror("sigaction SIGCHLD");
|
||||||
|
doExit(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Create execution result notification pipe. */
|
/* Create execution result notification pipe. */
|
||||||
if (pipe(chldPipe)) {
|
if (pipe(chldPipe)) {
|
||||||
@@ -175,6 +273,10 @@ int main(int argc, char *argv[])
|
|||||||
case 0:
|
case 0:
|
||||||
close(qtcFd);
|
close(qtcFd);
|
||||||
|
|
||||||
|
/* Remove the SIGCHLD handler from the child */
|
||||||
|
act.sa_handler = SIG_DFL;
|
||||||
|
sigaction(SIGCHLD, &act, 0);
|
||||||
|
|
||||||
/* Put the process into an own process group and make it the foregroud
|
/* Put the process into an own process group and make it the foregroud
|
||||||
* group on this terminal, so it will receive ctrl-c events, etc.
|
* group on this terminal, so it will receive ctrl-c events, etc.
|
||||||
* This is the main reason for *all* this stub magic in the first place. */
|
* This is the main reason for *all* this stub magic in the first place. */
|
||||||
@@ -200,57 +302,51 @@ int main(int argc, char *argv[])
|
|||||||
_exit(0);
|
_exit(0);
|
||||||
default:
|
default:
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (wait(&chldStatus) < 0) {
|
char buffer[100];
|
||||||
perror("Cannot obtain exit status of child process");
|
int nbytes;
|
||||||
doExit(3);
|
|
||||||
}
|
nbytes = read(qtcFd, buffer, 100);
|
||||||
if (WIFSTOPPED(chldStatus)) {
|
if (nbytes <= 0) {
|
||||||
/* The child stopped. This can be only the result of ptrace(TRACE_ME). */
|
if (nbytes < 0 && errno == EINTR)
|
||||||
/* We won't need the notification pipe any more, as we know that
|
continue;
|
||||||
* the exec() succeeded. */
|
if (!isDetached) {
|
||||||
close(chldPipe[0]);
|
isDetached = 2;
|
||||||
close(chldPipe[1]);
|
if (nbytes == 0)
|
||||||
chldPipe[0] = -1;
|
fprintf(stderr, "Lost connection to QtCreator, detaching from it.\n");
|
||||||
/* If we are not debugging, just skip the "handover enabler".
|
else
|
||||||
* This is suboptimal, as it makes us ignore setuid/-gid bits. */
|
perror("Lost connection to QtCreator, detaching from it");
|
||||||
if (!strcmp(argv[ArgAction], "debug")) {
|
|
||||||
/* Stop the child after we detach from it, so we can hand it over to gdb.
|
|
||||||
* If the signal delivery is not queued, things will go awry. It works on
|
|
||||||
* Linux and MacOSX ... */
|
|
||||||
kill(chldPid, SIGSTOP);
|
|
||||||
}
|
}
|
||||||
#ifdef __linux__
|
break;
|
||||||
ptrace(PTRACE_DETACH, chldPid, 0, 0);
|
} else {
|
||||||
#else
|
int i;
|
||||||
ptrace(PT_DETACH, chldPid, 0, 0);
|
for (i = 0; i < nbytes; ++i) {
|
||||||
#endif
|
switch (buffer[i]) {
|
||||||
sendMsg("pid %d\n", chldPid);
|
case 'k':
|
||||||
} else if (WIFEXITED(chldStatus)) {
|
if (chldPid > 0) {
|
||||||
/* The child exited normally. */
|
kill(chldPid, SIGTERM);
|
||||||
if (chldPipe[0] >= 0) {
|
sleep(1);
|
||||||
/* The child exited before being stopped by ptrace(). That can only
|
kill(chldPid, SIGKILL);
|
||||||
* mean that the exec() failed. */
|
}
|
||||||
switch (read(chldPipe[0], &errNo, sizeof(errNo))) {
|
break;
|
||||||
default:
|
case 'd':
|
||||||
/* Read of unknown length. Should never happen ... */
|
isDetached = 1;
|
||||||
errno = EPROTO;
|
break;
|
||||||
case -1:
|
case 's':
|
||||||
/* Read failed. Should never happen, either ... */
|
exit(0);
|
||||||
perror("Cannot read status from child process");
|
default:
|
||||||
doExit(3);
|
if (!hadInvalidCommand) {
|
||||||
case sizeof(errNo):
|
fprintf(stderr, "Ignoring invalid commands from QtCreator.\n");
|
||||||
/* Child telling us the errno from exec(). */
|
hadInvalidCommand = 1;
|
||||||
sendMsg("err:exec %d\n", errNo);
|
}
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendMsg("exit %d\n", WEXITSTATUS(chldStatus));
|
|
||||||
doExit(0);
|
|
||||||
} else {
|
|
||||||
sendMsg("crash %d\n", WTERMSIG(chldStatus));
|
|
||||||
doExit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
if (isDetached) {
|
||||||
|
for (;;)
|
||||||
|
pause(); /* will exit in the signal handler... */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert(0);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ bool CdbEngine::startConsole(const DebuggerStartParameters &sp, QString *errorMe
|
|||||||
SLOT(consoleStubError(QString)));
|
SLOT(consoleStubError(QString)));
|
||||||
connect(m_consoleStub.data(), SIGNAL(processStarted()),
|
connect(m_consoleStub.data(), SIGNAL(processStarted()),
|
||||||
SLOT(consoleStubProcessStarted()));
|
SLOT(consoleStubProcessStarted()));
|
||||||
connect(m_consoleStub.data(), SIGNAL(wrapperStopped()),
|
connect(m_consoleStub.data(), SIGNAL(stubStopped()),
|
||||||
SLOT(consoleStubExited()));
|
SLOT(consoleStubExited()));
|
||||||
m_consoleStub->setWorkingDirectory(sp.workingDirectory);
|
m_consoleStub->setWorkingDirectory(sp.workingDirectory);
|
||||||
if (sp.environment.size())
|
if (sp.environment.size())
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ void GdbTermEngine::setupEngine()
|
|||||||
|
|
||||||
connect(&m_stubProc, SIGNAL(processError(QString)), SLOT(stubError(QString)));
|
connect(&m_stubProc, SIGNAL(processError(QString)), SLOT(stubError(QString)));
|
||||||
connect(&m_stubProc, SIGNAL(processStarted()), SLOT(stubStarted()));
|
connect(&m_stubProc, SIGNAL(processStarted()), SLOT(stubStarted()));
|
||||||
connect(&m_stubProc, SIGNAL(wrapperStopped()), SLOT(stubExited()));
|
connect(&m_stubProc, SIGNAL(stubStopped()), SLOT(stubExited()));
|
||||||
// FIXME: Starting the stub implies starting the inferior. This is
|
// FIXME: Starting the stub implies starting the inferior. This is
|
||||||
// fairly unclean as far as the state machine and error reporting go.
|
// fairly unclean as far as the state machine and error reporting go.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user