diff --git a/src/tools/iostool/iosdevicemanager.cpp b/src/tools/iostool/iosdevicemanager.cpp index ce61347c610..48f7b4000cb 100644 --- a/src/tools/iostool/iosdevicemanager.cpp +++ b/src/tools/iostool/iosdevicemanager.cpp @@ -36,7 +36,9 @@ #include #include #include +#include #include + /* // annoying to import, do without #if QT_VERSION < 0x050000 #include @@ -60,8 +62,12 @@ #include "MobileDevice.h" #endif +// avoid utils dependency +#define QTC_OVERRIDE override + static const bool debugGdbServer = false; static const bool debugAll = false; +static const bool verbose = true; // ------- MobileDeviceLib interface -------- namespace { @@ -126,6 +132,8 @@ typedef am_res_t (MDEV_API *AMDeviceUninstallApplicationPtr)(ServiceSocket, CFSt AMDeviceInstallApplicationCallback, void*); typedef am_res_t (MDEV_API *AMDeviceLookupApplicationsPtr)(AMDeviceRef, CFDictionaryRef, CFDictionaryRef *); +typedef am_res_t (MDEV_API *USBMuxConnectByPortPtr)(unsigned int, int, ServiceSocket*); + } // extern C QString CFStringRef2QString(CFStringRef s) @@ -205,6 +213,7 @@ public : AMDeviceInstallApplicationCallback, void*); am_res_t deviceLookupApplications(AMDeviceRef, CFDictionaryRef, CFDictionaryRef *); + am_res_t connectByPort(unsigned int connectionId, int port, ServiceSocket *resFd); void addError(const QString &msg); void addError(const char *msg); @@ -231,6 +240,7 @@ private: AMDeviceInstallApplicationPtr m_AMDeviceInstallApplication; AMDeviceUninstallApplicationPtr m_AMDeviceUninstallApplication; AMDeviceLookupApplicationsPtr m_AMDeviceLookupApplications; + USBMuxConnectByPortPtr m_USBMuxConnectByPort; }; extern "C" { @@ -244,7 +254,8 @@ public: void *userData; }; -class CommandSession { +class CommandSession : public DeviceSession +{ public: virtual ~CommandSession(); explicit CommandSession(const QString &deviceId); @@ -262,6 +273,8 @@ public: bool startService(const QString &service, ServiceSocket &fd); void stopService(ServiceSocket fd); void startDeviceLookup(int timeout); + bool connectToPort(quint16 port, ServiceSocket *fd) QTC_OVERRIDE; + int qmljsDebugPort() const QTC_OVERRIDE; void addError(const QString &msg); bool writeAll(ServiceSocket fd, const char *cmd, qptrdiff len = -1); bool sendGdbCommand(ServiceSocket fd, const char *cmd, qptrdiff len = -1); @@ -271,7 +284,6 @@ public: MobileDeviceLib *lib(); - QString deviceId; AMDeviceRef device; int progressBase; int unexpectedChars; @@ -300,7 +312,7 @@ public: void didTransferApp(const QString &bundlePath, const QString &deviceId, Ios::IosDeviceManager::OpStatus status); void didStartApp(const QString &bundlePath, const QString &deviceId, - Ios::IosDeviceManager::OpStatus status, int fd); + Ios::IosDeviceManager::OpStatus status, int gdbFd, DeviceSession *deviceSession); void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress, const QString &info); void deviceWithId(QString deviceId, int timeout, DeviceAvailableCallback callback, void *userData); @@ -334,6 +346,7 @@ public: void deviceCallbackReturned(); bool installApp(); bool runApp(); + int qmljsDebugPort() const QTC_OVERRIDE; am_res_t appTransferCallback(CFDictionaryRef dict); am_res_t appInstallCallback(CFDictionaryRef dict); void reportProgress2(int progress, const QString &status); @@ -342,7 +355,18 @@ public: QString commandName(); }; -} // namespace Internal +} + +DeviceSession::DeviceSession(const QString &deviceId) : + deviceId(deviceId) +{ +} + +DeviceSession::~DeviceSession() +{ +} + +// namespace Internal } // namespace Ios @@ -556,9 +580,11 @@ void IosDeviceManagerPrivate::didTransferApp(const QString &bundlePath, const QS } void IosDeviceManagerPrivate::didStartApp(const QString &bundlePath, const QString &deviceId, - IosDeviceManager::OpStatus status, int fd) + IosDeviceManager::OpStatus status, int gdbFd, + DeviceSession *deviceSession) { - emit IosDeviceManagerPrivate::instance()->q->didStartApp(bundlePath, deviceId, status, fd); + emit IosDeviceManagerPrivate::instance()->q->didStartApp(bundlePath, deviceId, status, gdbFd, + deviceSession); } void IosDeviceManagerPrivate::isTransferringApp(const QString &bundlePath, const QString &deviceId, @@ -709,7 +735,7 @@ int IosDeviceManagerPrivate::processGdbServer(int fd) // ------- ConnectSession implementation -------- -CommandSession::CommandSession(const QString &deviceId) : deviceId(deviceId), device(0), +CommandSession::CommandSession(const QString &deviceId) : DeviceSession(deviceId), device(0), progressBase(0), unexpectedChars(0), aknowledge(true) { } @@ -777,6 +803,32 @@ bool CommandSession::startService(const QString &serviceName, ServiceSocket &fd) return !failure; } +bool CommandSession::connectToPort(quint16 port, ServiceSocket *fd) +{ + if (!fd) + return false; + bool failure = false; + *fd = 0; + ServiceSocket fileDescriptor; + if (!connectDevice()) + return false; + if (am_res_t error = lib()->connectByPort(lib()->deviceGetConnectionID(device), htons(port), &fileDescriptor)) { + addError(QString::fromLatin1("connectByPort on device %1 port %2 failed, AMDeviceStartService returned %3") + .arg(deviceId).arg(port).arg(error)); + failure = true; + *fd = -1; + } else { + *fd = fileDescriptor; + } + disconnectDevice(); + return !failure; +} + +int CommandSession::qmljsDebugPort() const +{ + return 0; +} + void CommandSession::stopService(ServiceSocket fd) { // would be close socket on windows @@ -791,7 +843,7 @@ void CommandSession::startDeviceLookup(int timeout) void CommandSession::addError(const QString &msg) { - if (debugAll) + if (verbose) qDebug() << "CommandSession ERROR: " << msg; IosDeviceManagerPrivate::instance()->addError(commandName() + msg); } @@ -985,7 +1037,7 @@ bool CommandSession::expectGdbReply(ServiceSocket gdbFd, QByteArray expected) { QByteArray repl = readGdbReply(gdbFd); if (repl != expected) { - addError(QString::fromLatin1("Unexpected reply: %1 (%2) vs %3 (%3)") + addError(QString::fromLatin1("Unexpected reply: %1 (%2) vs %3 (%4)") .arg(QString::fromLocal8Bit(repl.constData(), repl.size())) .arg(QString::fromLatin1(repl.toHex().constData(), 2*repl.size())) .arg(QString::fromLocal8Bit(expected.constData(), expected.size())) @@ -1090,6 +1142,20 @@ void AppOpSession::deviceCallbackReturned() } } +int AppOpSession::qmljsDebugPort() const +{ + QRegExp qmlPortRe = QRegExp(QLatin1String("-qmljsdebugger=port:([0-9]+)")); + foreach (const QString &arg, extraArgs) { + if (qmlPortRe.indexIn(arg) == 0) { + bool ok; + int res = qmlPortRe.cap(1).toInt(&ok); + if (ok && res >0 && res <= 0xFFFF) + return res; + } + } + return 0; +} + bool AppOpSession::runApp() { bool failure = (device == 0); @@ -1128,7 +1194,7 @@ bool AppOpSession::runApp() } IosDeviceManagerPrivate::instance()->didStartApp( bundlePath, deviceId, - (failure ? IosDeviceManager::Failure : IosDeviceManager::Success), gdbFd); + (failure ? IosDeviceManager::Failure : IosDeviceManager::Success), gdbFd, this); return !failure; } @@ -1326,6 +1392,7 @@ bool MobileDeviceLib::load() m_AMDeviceInstallApplication = &AMDeviceInstallApplication; //m_AMDeviceUninstallApplication = &AMDeviceUninstallApplication; //m_AMDeviceLookupApplications = &AMDeviceLookupApplications; + m_USBMuxConnectByPort = &USBMuxConnectByPort; #else QLibrary *libAppleFSCompression = new QLibrary(QLatin1String("/System/Library/PrivateFrameworks/AppleFSCompression.framework/AppleFSCompression")); if (!libAppleFSCompression->load()) @@ -1395,6 +1462,9 @@ bool MobileDeviceLib::load() m_AMDeviceLookupApplications = reinterpret_cast(lib.resolve("AMDeviceLookupApplications")); if (m_AMDeviceLookupApplications == 0) addError("MobileDeviceLib does not define AMDeviceLookupApplications"); + m_USBMuxConnectByPort = reinterpret_cast(lib.resolve("USBMuxConnectByPort")); + if (m_USBMuxConnectByPort == 0) + addError("MobileDeviceLib does not define USBMuxConnectByPort"); #endif return true; } @@ -1557,6 +1627,13 @@ am_res_t MobileDeviceLib::deviceLookupApplications(AMDeviceRef device, CFDiction return -1; } +am_res_t MobileDeviceLib::connectByPort(unsigned int connectionId, int port, ServiceSocket *resFd) +{ + if (m_USBMuxConnectByPort) + return m_USBMuxConnectByPort(connectionId, port, resFd); + return -1; +} + void MobileDeviceLib::addError(const QString &msg) { qDebug() << "MobileDeviceLib ERROR:" << msg; diff --git a/src/tools/iostool/iosdevicemanager.h b/src/tools/iostool/iosdevicemanager.h index e5c5aacc4a7..fddcdb2f337 100644 --- a/src/tools/iostool/iosdevicemanager.h +++ b/src/tools/iostool/iosdevicemanager.h @@ -45,6 +45,10 @@ class DevInfoSession; class IosDeviceManagerPrivate; } // namespace Internal +typedef unsigned int ServiceSocket; + +class DeviceSession; + class IosDeviceManager : public QObject { Q_OBJECT @@ -77,7 +81,8 @@ signals: void didTransferApp(const QString &bundlePath, const QString &deviceId, Ios::IosDeviceManager::OpStatus status); void didStartApp(const QString &bundlePath, const QString &deviceId, - Ios::IosDeviceManager::OpStatus status, int gdbFd); + Ios::IosDeviceManager::OpStatus status, int gdbFd, + Ios::DeviceSession *deviceSession); void deviceInfo(const QString &deviceId, const Ios::IosDeviceManager::Dict &info); void appOutput(const QString &output); void errorMsg(const QString &msg); @@ -90,6 +95,15 @@ private: Internal::IosDeviceManagerPrivate *d; }; +class DeviceSession { +public: + DeviceSession(const QString &deviceId); + virtual ~DeviceSession(); + QString deviceId; + virtual int qmljsDebugPort() const = 0; + virtual bool connectToPort(quint16 port, ServiceSocket *fd) = 0; +}; + } // namespace Ios #endif // IOSMANAGER_H diff --git a/src/tools/iostool/main.cpp b/src/tools/iostool/main.cpp index 0d2c4cdf929..1ef48f8bc5d 100644 --- a/src/tools/iostool/main.cpp +++ b/src/tools/iostool/main.cpp @@ -26,6 +26,8 @@ ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ +#include "iosdevicemanager.h" + #include #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include @@ -41,8 +43,12 @@ #include #include #include +#include +#include +#include +#include +#include -#include "iosdevicemanager.h" #include #include #include @@ -53,9 +59,111 @@ #include #endif -static const bool debugGdbEchoServer = false; +// avoid utils dependency +#define QTC_OVERRIDE override +#define QTC_CHECK(cond) if (cond) {} else { qWarning() << "assert failed " << #cond << " " \ + << __FILE__ << ":" << __LINE__; } do {} while (0) -class IosTool: public QObject { +static bool echoRelays = false; + +class IosTool; +class RelayServer; +class GenericRelayServer; + +class Relayer: public QObject +{ + Q_OBJECT +public: + Relayer(RelayServer *parent, QTcpSocket *clientSocket); + ~Relayer(); + void setClientSocket(QTcpSocket *clientSocket); + bool startRelay(int serverFileDescriptor); +public slots: + void handleSocketHasData(int socket); + void handleClientHasData(); + void handleClientHasError(QAbstractSocket::SocketError error); +protected: + IosTool *iosTool(); + RelayServer *server(); + Ios::ServiceSocket m_serverFileDescriptor; + QTcpSocket *m_clientSocket; + QSocketNotifier *m_serverNotifier; +}; + +class RemotePortRelayer: public Relayer +{ + Q_OBJECT +public: + static const int reconnectMsecDelay = 500; + static const int maxReconnectAttempts = 2*60*5; // 5 min + RemotePortRelayer(GenericRelayServer *parent, QTcpSocket *clientSocket); +public slots: + void tryRemoteConnect(); +signals: + void didConnect(GenericRelayServer *serv); +private: + QTimer m_remoteConnectTimer; +}; + +class RelayServer: public QObject +{ + Q_OBJECT +public: + RelayServer(IosTool *parent); + ~RelayServer(); + bool startServer(int port, bool ipv6); + void stopServer(); + quint16 serverPort(); + IosTool *iosTool(); +public slots: + void handleNewRelayConnection(); +protected: + virtual void newRelayConnection() = 0; + + QTcpServer m_server; + QList m_connections; +}; + +class SingleRelayServer: public RelayServer +{ + Q_OBJECT +public: + SingleRelayServer(IosTool *parent, int serverFileDescriptor); +protected: + void newRelayConnection() QTC_OVERRIDE; +private: + int m_serverFileDescriptor; +}; + +class GenericRelayServer: public RelayServer { + Q_OBJECT +public: + GenericRelayServer(IosTool *parent, int remotePort, + Ios::DeviceSession *deviceSession); +protected: + void newRelayConnection() QTC_OVERRIDE; +private: + int m_remotePort; + Ios::DeviceSession *m_deviceSession; + friend class RemotePortRelayer; +}; + +class GdbRunner: public QObject +{ + Q_OBJECT +public: + GdbRunner(IosTool *iosTool, int gdbFd); +public slots: + void run(); +signals: + void finished(); +private: + IosTool *m_iosTool; + int m_gdbFd; +}; + +class IosTool: public QObject +{ Q_OBJECT public: IosTool(QObject *parent = 0); @@ -66,24 +174,22 @@ public: void writeMsg(const QString &msg); void stopXml(int errorCode); void writeTextInElement(const QString &output); + void stopRelayServers(int errorCode = 0); + void writeMaybeBin(const QString &extraMsg, const char *msg, quintptr len); +public slots: + void errorMsg(const QString &msg); private slots: void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress, const QString &info); void didTransferApp(const QString &bundlePath, const QString &deviceId, Ios::IosDeviceManager::OpStatus status); void didStartApp(const QString &bundlePath, const QString &deviceId, - Ios::IosDeviceManager::OpStatus status, int gdbFd); + Ios::IosDeviceManager::OpStatus status, int gdbFd, + Ios::DeviceSession *deviceSession); void deviceInfo(const QString &deviceId, const Ios::IosDeviceManager::Dict &info); void appOutput(const QString &output); - void errorMsg(const QString &msg); - void handleNewConnection(); - void handleGdbServerSocketHasData(int socket); - void handleCreatorHasData(); - void handleCreatorHasError(QAbstractSocket::SocketError error); private: - bool startServer(); - void stopGdbServer(int errorCode = 0); - + QMutex m_xmlMutex; int maxProgress; int opLeft; bool debug; @@ -92,15 +198,301 @@ private: bool splitAppOutput; // as QXmlStreamReader reports the text attributes atomically it is better to split Ios::IosDeviceManager::AppOp appOp; QFile outFile; + QString m_qmlPort; QXmlStreamWriter out; - int gdbFileDescriptor; - QTcpSocket *creatorSocket; - QSocketNotifier *gdbServerNotifier; - QTcpServer gdbServer; + SingleRelayServer *gdbServer; + GenericRelayServer *qmlServer; + friend class GdbRunner; }; +Relayer::Relayer(RelayServer *parent, QTcpSocket *clientSocket) : + QObject(parent), m_serverFileDescriptor(0), m_clientSocket(0), m_serverNotifier(0) +{ + setClientSocket(clientSocket); +} + +Relayer::~Relayer() +{ + if (m_serverFileDescriptor > 0) { + ::close(m_serverFileDescriptor); + m_serverFileDescriptor = -1; + if (m_serverNotifier) + delete m_serverNotifier; + } + if (m_clientSocket->isOpen()) + m_clientSocket->close(); + delete m_clientSocket; +} + +void Relayer::setClientSocket(QTcpSocket *clientSocket) +{ + QTC_CHECK(!m_clientSocket); + m_clientSocket = clientSocket; + if (m_clientSocket) + connect(m_clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), + SLOT(handleClientHasError(QAbstractSocket::SocketError))); +} + +bool Relayer::startRelay(int serverFileDescriptor) +{ + QTC_CHECK(!m_serverFileDescriptor); + m_serverFileDescriptor = serverFileDescriptor; + if (!m_clientSocket || m_serverFileDescriptor <= 0) + return false; + fcntl(serverFileDescriptor,F_SETFL, fcntl(serverFileDescriptor, F_GETFL) | O_NONBLOCK); + connect(m_clientSocket, SIGNAL(readyRead()), SLOT(handleClientHasData())); + m_serverNotifier = new QSocketNotifier(m_serverFileDescriptor, QSocketNotifier::Read, this); + connect(m_serverNotifier, SIGNAL(activated(int)), SLOT(handleSocketHasData(int))); + // no way to check if an error did happen? + if (m_clientSocket->bytesAvailable() > 0) + handleClientHasData(); + return true; +} + +void Relayer::handleSocketHasData(int socket) +{ + m_serverNotifier->setEnabled(false); + char buf[255]; + while (true) { + qptrdiff rRead = read(socket, &buf, sizeof(buf)-1); + if (rRead == -1) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) { + m_serverNotifier->setEnabled(true); + return; + } + iosTool()->errorMsg(qt_error_string(errno)); + close(socket); + iosTool()->stopRelayServers(-1); + return; + } + if (rRead == 0) { + iosTool()->stopRelayServers(0); + return; + } + if (echoRelays) { + iosTool()->writeMaybeBin(QString::fromLatin1("%1 serverReplies:") + .arg((quintptr)(void *)this), buf, rRead); + } + qint64 pos = 0; + while (true) { + qint64 writtenNow = m_clientSocket->write(buf + int(pos), rRead); + if (writtenNow == -1) { + iosTool()->writeMsg(m_clientSocket->errorString()); + iosTool()->stopRelayServers(-1); + return; + } + if (writtenNow < rRead) { + pos += writtenNow; + rRead -= qptrdiff(writtenNow); + } else { + break; + } + } + m_clientSocket->flush(); + } +} + +void Relayer::handleClientHasData() +{ + char buf[255]; + while (true) { + qint64 toRead = m_clientSocket->bytesAvailable(); + if (qint64(sizeof(buf)-1) < toRead) + toRead = sizeof(buf)-1; + qint64 rRead = m_clientSocket->read(buf, toRead); + if (rRead == -1) { + iosTool()->errorMsg(m_clientSocket->errorString()); + iosTool()->stopRelayServers(); + return; + } + if (rRead == 0) { + if (!m_clientSocket->isOpen()) + iosTool()->stopRelayServers(); + return; + } + int pos = 0; + int irep = 0; + if (echoRelays) { + iosTool()->writeMaybeBin(QString::fromLatin1("%1 clientAsks:") + .arg((quintptr)(void *)this), buf, rRead); + } + while (true) { + qptrdiff written = write(m_serverFileDescriptor, buf + pos, rRead); + if (written == -1) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) { + if (++irep > 10) { + sleep(1); + irep = 0; + } + continue; + } + iosTool()->errorMsg(qt_error_string(errno)); + iosTool()->stopRelayServers(); + return; + } + if (written == 0) { + iosTool()->stopRelayServers(); + return; + } + if (written < rRead) { + pos += written; + rRead -= written; + } else { + break; + } + } + } +} + +void Relayer::handleClientHasError(QAbstractSocket::SocketError error) +{ + iosTool()->errorMsg(tr("iOS Debugging connection to creator failed with error %1").arg(error)); + iosTool()->stopRelayServers(); +} + +IosTool *Relayer::iosTool() +{ + return (server() ? server()->iosTool() : 0); +} + +RelayServer *Relayer::server() +{ + return qobject_cast(parent()); +} + +RemotePortRelayer::RemotePortRelayer(GenericRelayServer *parent, QTcpSocket *clientSocket) : + Relayer(parent, clientSocket) +{ + m_remoteConnectTimer.setSingleShot(true); + m_remoteConnectTimer.setInterval(reconnectMsecDelay); + connect(&m_remoteConnectTimer, SIGNAL(timeout()), + SLOT(tryRemoteConnect())); +} + +void RemotePortRelayer::tryRemoteConnect() +{ + iosTool()->errorMsg(QLatin1String("tryRemoteConnect")); + if (m_serverFileDescriptor > 0) + return; + Ios::ServiceSocket ss; + GenericRelayServer *grServer = qobject_cast(server()); + if (!grServer) + return; + if (grServer->m_deviceSession->connectToPort(grServer->m_remotePort, &ss)) { + if (ss > 0) { + iosTool()->errorMsg(QString::fromLatin1("tryRemoteConnect *succeeded* on remote port %1") + .arg(grServer->m_remotePort)); + startRelay(ss); + emit didConnect(grServer); + return; + } + } + iosTool()->errorMsg(QString::fromLatin1("tryRemoteConnect *failed* on remote port %1") + .arg(grServer->m_remotePort)); + m_remoteConnectTimer.start(); +} + +RelayServer::RelayServer(IosTool *parent): + QObject(parent) +{ } + +RelayServer::~RelayServer() +{ + stopServer(); +} + +bool RelayServer::startServer(int port, bool ipv6) +{ + QTC_CHECK(!m_server.isListening()); + m_server.setMaxPendingConnections(1); + connect(&m_server, SIGNAL(newConnection()), SLOT(handleNewRelayConnection())); + quint16 portValue = static_cast(port); + if (port < 0 || port > 0xFFFF) + return false; + if (ipv6) + return m_server.listen(QHostAddress(QHostAddress::LocalHostIPv6), portValue); + else + return m_server.listen(QHostAddress(QHostAddress::LocalHost), portValue); +} + +void RelayServer::stopServer() +{ + foreach (Relayer *connection, m_connections) + delete connection; + if (m_server.isListening()) + m_server.close(); +} + +quint16 RelayServer::serverPort() +{ + return m_server.serverPort(); +} + +IosTool *RelayServer::iosTool() +{ + return qobject_cast(parent()); +} + +void RelayServer::handleNewRelayConnection() +{ + iosTool()->errorMsg(QLatin1String("handleNewRelayConnection")); + newRelayConnection(); +} + +SingleRelayServer::SingleRelayServer(IosTool *parent, + int serverFileDescriptor) : + RelayServer(parent) +{ + m_serverFileDescriptor = serverFileDescriptor; + if (m_serverFileDescriptor > 0) + fcntl(m_serverFileDescriptor, F_SETFL, fcntl(m_serverFileDescriptor, F_GETFL, 0) | O_NONBLOCK); +} + +void SingleRelayServer::newRelayConnection() +{ + if (m_connections.size() > 0) { + m_server.close(); + QTcpSocket *s = m_server.nextPendingConnection(); + delete s; + return; + } + QTcpSocket *clientSocket = m_server.nextPendingConnection(); + if (clientSocket) { + Relayer *newConnection = new Relayer(this, clientSocket); + m_connections.append(newConnection); + newConnection->startRelay(m_serverFileDescriptor); + } + m_server.close(); +} + +GenericRelayServer::GenericRelayServer(IosTool *parent, int remotePort, + Ios::DeviceSession *deviceSession) : + RelayServer(parent), + m_remotePort(remotePort), + m_deviceSession(deviceSession) +{ + parent->errorMsg(QLatin1String("created qml server")); +} + + +void GenericRelayServer::newRelayConnection() +{ + QTcpSocket *clientSocket = m_server.nextPendingConnection(); + if (clientSocket) { + iosTool()->errorMsg(QString::fromLatin1("setting up relayer for new connection")); + RemotePortRelayer *newConnection = new RemotePortRelayer(this, clientSocket); + m_connections.append(newConnection); + newConnection->tryRemoteConnect(); + } +} + IosTool::IosTool(QObject *parent): QObject(parent), + m_xmlMutex(QMutex::Recursive), maxProgress(0), opLeft(0), debug(false), @@ -110,9 +502,8 @@ IosTool::IosTool(QObject *parent): appOp(Ios::IosDeviceManager::None), outFile(), out(&outFile), - gdbFileDescriptor(-1), - creatorSocket(0), - gdbServerNotifier(0) + gdbServer(0), + qmlServer(0) { #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) outFile.open(stdout, QIODevice::WriteOnly, QFile::DontCloseHandle); @@ -125,8 +516,6 @@ IosTool::IosTool(QObject *parent): IosTool::~IosTool() { - delete creatorSocket; // not strictly required - delete gdbServerNotifier; // not strictly required } void IosTool::run(const QStringList &args) @@ -161,6 +550,8 @@ void IosTool::run(const QStringList &args) appOp = Ios::IosDeviceManager::AppOp(appOp | Ios::IosDeviceManager::Run); } else if (arg == QLatin1String("-ipv6")) { ipv6 = true; + } else if (arg == QLatin1String("-verbose")) { + echoRelays = true; } else if (arg == QLatin1String("-debug")) { appOp = Ios::IosDeviceManager::AppOp(appOp | Ios::IosDeviceManager::Run); debug = true; @@ -180,8 +571,8 @@ void IosTool::run(const QStringList &args) printHelp = true; } } else if (arg == QLatin1String("-extra-args")) { - ++iarg; extraArgs = args.mid(iarg, args.size() - iarg); + iarg = args.size(); } else if (arg == QLatin1String("-help") || arg == QLatin1String("--help")) { printHelp = true; } else { @@ -191,7 +582,7 @@ void IosTool::run(const QStringList &args) if (printHelp) { out.writeStartElement(QLatin1String("msg")); out.writeCharacters(QLatin1String("iosTool [-device-id ] [-bundle ] [-deploy] [-run] [-debug]\n")); - out.writeCharacters(QLatin1String(" [-device-info] [-timeout ]\n")); // to do pass in env as stub does + out.writeCharacters(QLatin1String(" [-device-info] [-timeout ] [-verbose]\n")); // to do pass in env as stub does out.writeCharacters(QLatin1String(" [-extra-args ]")); out.writeEndElement(); doExit(-1); @@ -202,13 +593,23 @@ void IosTool::run(const QStringList &args) SLOT(isTransferringApp(QString,QString,int,QString))); connect(manager,SIGNAL(didTransferApp(QString,QString,Ios::IosDeviceManager::OpStatus)), SLOT(didTransferApp(QString,QString,Ios::IosDeviceManager::OpStatus))); - connect(manager,SIGNAL(didStartApp(QString,QString,Ios::IosDeviceManager::OpStatus,int)), - SLOT(didStartApp(QString,QString,Ios::IosDeviceManager::OpStatus,int))); + connect(manager,SIGNAL(didStartApp(QString,QString,Ios::IosDeviceManager::OpStatus,int,Ios::DeviceSession *)), + SLOT(didStartApp(QString,QString,Ios::IosDeviceManager::OpStatus,int,Ios::DeviceSession *))); connect(manager,SIGNAL(deviceInfo(QString,Ios::IosDeviceManager::Dict)), SLOT(deviceInfo(QString,Ios::IosDeviceManager::Dict))); connect(manager,SIGNAL(appOutput(QString)), SLOT(appOutput(QString))); connect(manager,SIGNAL(errorMsg(QString)), SLOT(errorMsg(QString))); manager->watchDevices(); + QRegExp qmlPortRe=QRegExp(QLatin1String("-qmljsdebugger=port:([0-9]+)")); + foreach (const QString &arg, extraArgs) { + if (qmlPortRe.indexIn(arg) == 0) { + bool ok; + int qmlPort = qmlPortRe.cap(1).toInt(&ok); + if (ok && qmlPort > 0 && qmlPort <= 0xFFFF) + m_qmlPort = qmlPortRe.cap(1); + break; + } + } if (deviceInfo) { if (!bundlePath.isEmpty()) writeMsg("-device-info overrides bundlePath"); @@ -235,6 +636,7 @@ void IosTool::run(const QStringList &args) void IosTool::stopXml(int errorCode) { + QMutexLocker l(&m_xmlMutex); out.writeEmptyElement(QLatin1String("exit")); out.writeAttribute(QLatin1String("code"), QString::number(errorCode)); out.writeEndElement(); // result element (hopefully) @@ -254,6 +656,7 @@ void IosTool::isTransferringApp(const QString &bundlePath, const QString &device { Q_UNUSED(bundlePath); Q_UNUSED(deviceId); + QMutexLocker l(&m_xmlMutex); out.writeStartElement(QLatin1String("status")); out.writeAttribute(QLatin1String("progress"), QString::number(progress)); out.writeAttribute(QLatin1String("max_progress"), QString::number(maxProgress)); @@ -267,36 +670,43 @@ void IosTool::didTransferApp(const QString &bundlePath, const QString &deviceId, { Q_UNUSED(bundlePath); Q_UNUSED(deviceId); - if (status == Ios::IosDeviceManager::Success) { - out.writeStartElement(QLatin1String("status")); - out.writeAttribute(QLatin1String("progress"), QString::number(maxProgress)); - out.writeAttribute(QLatin1String("max_progress"), QString::number(maxProgress)); - out.writeCharacters(QLatin1String("App Transferred")); - out.writeEndElement(); + { + QMutexLocker l(&m_xmlMutex); + if (status == Ios::IosDeviceManager::Success) { + out.writeStartElement(QLatin1String("status")); + out.writeAttribute(QLatin1String("progress"), QString::number(maxProgress)); + out.writeAttribute(QLatin1String("max_progress"), QString::number(maxProgress)); + out.writeCharacters(QLatin1String("App Transferred")); + out.writeEndElement(); + } + out.writeEmptyElement(QLatin1String("app_transfer")); + out.writeAttribute(QLatin1String("status"), + (status == Ios::IosDeviceManager::Success) ? + QLatin1String("SUCCESS") : + QLatin1String("FAILURE")); + //out.writeCharacters(QString()); // trigger a complete closing of the empty element + outFile.flush(); } - out.writeEmptyElement(QLatin1String("app_transfer")); - out.writeAttribute(QLatin1String("status"), - (status == Ios::IosDeviceManager::Success) ? - QLatin1String("SUCCESS") : - QLatin1String("FAILURE")); - //out.writeCharacters(QString()); // trigger a complete closing of the empty element - outFile.flush(); if (status != Ios::IosDeviceManager::Success || --opLeft == 0) doExit((status == Ios::IosDeviceManager::Success) ? 0 : -1); } void IosTool::didStartApp(const QString &bundlePath, const QString &deviceId, - Ios::IosDeviceManager::OpStatus status, int gdbFd) + Ios::IosDeviceManager::OpStatus status, int gdbFd, + Ios::DeviceSession *deviceSession) { Q_UNUSED(bundlePath); Q_UNUSED(deviceId); - out.writeEmptyElement(QLatin1String("app_started")); - out.writeAttribute(QLatin1String("status"), - (status == Ios::IosDeviceManager::Success) ? - QLatin1String("SUCCESS") : - QLatin1String("FAILURE")); - //out.writeCharacters(QString()); // trigger a complete closing of the empty element - outFile.flush(); + { + QMutexLocker l(&m_xmlMutex); + out.writeEmptyElement(QLatin1String("app_started")); + out.writeAttribute(QLatin1String("status"), + (status == Ios::IosDeviceManager::Success) ? + QLatin1String("SUCCESS") : + QLatin1String("FAILURE")); + //out.writeCharacters(QString()); // trigger a complete closing of the empty element + outFile.flush(); + } if (status != Ios::IosDeviceManager::Success || appOp == Ios::IosDeviceManager::Install) { doExit(); return; @@ -311,29 +721,45 @@ void IosTool::didStartApp(const QString &bundlePath, const QString &deviceId, doExit(-3); return; } + if (deviceSession) { + int qmlPort = deviceSession->qmljsDebugPort(); + if (qmlPort) { + qmlServer = new GenericRelayServer(this, qmlPort, deviceSession); + qmlServer->startServer(0, ipv6); + } + } if (debug) { - gdbFileDescriptor=gdbFd; - if (!startServer()) { + gdbServer = new SingleRelayServer(this, gdbFd); + if (!gdbServer->startServer(0, ipv6)) { doExit(-4); return; } - out.writeTextElement(QLatin1String("gdb_server_port"), - QString::number(gdbServer.serverPort())); + } + { + QMutexLocker l(&m_xmlMutex); + out.writeStartElement(QLatin1String("server_ports")); + out.writeAttribute(QLatin1String("gdb_server"), + QString::number(gdbServer ? gdbServer->serverPort() : 0)); + out.writeAttribute(QLatin1String("qml_server"), + QString::number(qmlServer ? qmlServer->serverPort() : 0)); + out.writeEndElement(); outFile.flush(); - } else { - if (!splitAppOutput) { - out.writeStartElement(QLatin1String("app_output")); - inAppOutput = true; + } + if (!debug) { + GdbRunner *gdbRunner = new GdbRunner(this, gdbFd); + if (qmlServer) { + // we should not stop the event handling of the main thread + // all output moves to the new thread (other option would be to signal it back) + QThread *gdbProcessThread = new QThread(); + gdbRunner->moveToThread(gdbProcessThread); + QObject::connect(gdbProcessThread, SIGNAL(started()), gdbRunner, SLOT(run())); + QObject::connect(gdbRunner, SIGNAL(finished()), gdbProcessThread, SLOT(quit())); + QObject::connect(gdbProcessThread, SIGNAL(finished()), gdbProcessThread, SLOT(deleteLater())); + gdbProcessThread->start(); + } else { + gdbRunner->setParent(this); + gdbRunner->run(); } - outFile.flush(); - Ios::IosDeviceManager::instance()->processGdbServer(gdbFd); - if (!splitAppOutput) { - inAppOutput = false; - out.writeEndElement(); - } - outFile.flush(); - close(gdbFd); - doExit(); } } @@ -344,6 +770,7 @@ void IosTool::writeMsg(const char *msg) void IosTool::writeMsg(const QString &msg) { + QMutexLocker l(&m_xmlMutex); out.writeStartElement(QLatin1String("msg")); writeTextInElement(msg); out.writeCharacters(QLatin1String("\n")); @@ -351,21 +778,54 @@ void IosTool::writeMsg(const QString &msg) outFile.flush(); } +void IosTool::writeMaybeBin(const QString &extraMsg, const char *msg, quintptr len) +{ + char *buf2 = new char[len * 2 + 4]; + buf2[0] = '['; + const char toHex[] = "0123456789abcdef"; + for (quintptr i = 0; i < len; ++i) { + buf2[2 * i + 1] = toHex[(0xF & (msg[i] >> 4))]; + buf2[2 * i + 2] = toHex[(0xF & msg[i])]; + } + buf2[2 * len + 1] = ']'; + buf2[2 * len + 2] = ' '; + buf2[2 * len + 3] = 0; + + QMutexLocker l(&m_xmlMutex); + out.writeStartElement(QLatin1String("msg")); + out.writeCharacters(extraMsg); + out.writeCharacters(QLatin1String(buf2)); + for (quintptr i = 0; i < len; ++i) { + if (msg[i] < 0x20 || msg[i] > 0x7f) + buf2[i] = '_'; + else + buf2[i] = msg[i]; + } + buf2[len] = 0; + out.writeCharacters(QLatin1String(buf2)); + delete[] buf2; + out.writeEndElement(); + outFile.flush(); +} + void IosTool::deviceInfo(const QString &deviceId, const Ios::IosDeviceManager::Dict &devInfo) { Q_UNUSED(deviceId); - out.writeTextElement(QLatin1String("device_id"), deviceId); - out.writeStartElement(QLatin1String("device_info")); - QMapIterator i(devInfo); - while (i.hasNext()) { - i.next(); - out.writeStartElement(QLatin1String("item")); - out.writeTextElement(QLatin1String("key"), i.key()); - out.writeTextElement(QLatin1String("value"), i.value()); + { + QMutexLocker l(&m_xmlMutex); + out.writeTextElement(QLatin1String("device_id"), deviceId); + out.writeStartElement(QLatin1String("device_info")); + QMapIterator i(devInfo); + while (i.hasNext()) { + i.next(); + out.writeStartElement(QLatin1String("item")); + out.writeTextElement(QLatin1String("key"), i.key()); + out.writeTextElement(QLatin1String("value"), i.value()); + out.writeEndElement(); + } out.writeEndElement(); + outFile.flush(); } - out.writeEndElement(); - outFile.flush(); doExit(); } @@ -376,6 +836,7 @@ void IosTool::writeTextInElement(const QString &output) int oldPos = 0; while ((pos = controlCharRe.indexIn(output, pos)) != -1) { + QMutexLocker l(&m_xmlMutex); out.writeCharacters(output.mid(oldPos, pos - oldPos)); out.writeEmptyElement(QLatin1String("control_char")); out.writeAttribute(QLatin1String("code"), QString::number(output.at(pos).toLatin1())); @@ -387,6 +848,7 @@ void IosTool::writeTextInElement(const QString &output) void IosTool::appOutput(const QString &output) { + QMutexLocker l(&m_xmlMutex); if (!inAppOutput) out.writeStartElement(QLatin1String("app_output")); writeTextInElement(output); @@ -400,172 +862,71 @@ void IosTool::errorMsg(const QString &msg) writeMsg(msg); } -void IosTool::handleNewConnection() +void IosTool::stopRelayServers(int errorCode) { - if (creatorSocket) { - gdbServer.close(); - QTcpSocket *s = gdbServer.nextPendingConnection(); - delete s; - return; - } - creatorSocket = gdbServer.nextPendingConnection(); - connect(creatorSocket, SIGNAL(readyRead()), SLOT(handleCreatorHasData())); - connect(creatorSocket, SIGNAL(error(QAbstractSocket::SocketError)), - SLOT(handleCreatorHasError(QAbstractSocket::SocketError))); - gdbServerNotifier = new QSocketNotifier(gdbFileDescriptor, QSocketNotifier::Read, this); - connect(gdbServerNotifier, SIGNAL(activated(int)), SLOT(handleGdbServerSocketHasData(int))); - gdbServer.close(); -} - -void IosTool::handleGdbServerSocketHasData(int socket) -{ - gdbServerNotifier->setEnabled(false); - char buf[255]; - while (true) { - qptrdiff rRead = read(socket, &buf, sizeof(buf)-1); - if (rRead == -1) { - if (errno == EINTR) - continue; - if (errno == EAGAIN) { - gdbServerNotifier->setEnabled(true); - return; - } - errorMsg(qt_error_string(errno)); - close(socket); - stopGdbServer(-1); - return; - } - if (rRead == 0) { - stopGdbServer(0); - return; - } - if (debugGdbEchoServer) { - writeMsg("gdbServerReplies:"); - buf[rRead] = 0; - writeMsg(buf); - } - qint64 pos = 0; - while (true) { - qint64 writtenNow = creatorSocket->write(buf + int(pos), rRead); - if (writtenNow == -1) { - writeMsg(creatorSocket->errorString()); - stopGdbServer(-1); - return; - } - if (writtenNow < rRead) { - pos += writtenNow; - rRead -= qptrdiff(writtenNow); - } else { - break; - } - } - } -} - -void IosTool::stopGdbServer(int errorCode) -{ - if (debugGdbEchoServer) + if (echoRelays) writeMsg("gdbServerStops"); - if (!creatorSocket) - return; - if (gdbFileDescriptor > 0) { - ::close(gdbFileDescriptor); - gdbFileDescriptor = -1; - if (gdbServerNotifier) - delete gdbServerNotifier; - } - if (creatorSocket->isOpen()) - creatorSocket->close(); - delete creatorSocket; + if (qmlServer) + qmlServer->stopServer(); + if (gdbServer) + gdbServer->stopServer(); doExit(errorCode); } -void IosTool::handleCreatorHasData() -{ - char buf[255]; - while (true) { - qint64 toRead = creatorSocket->bytesAvailable(); - if (qint64(sizeof(buf)-1) < toRead) - toRead = sizeof(buf)-1; - qint64 rRead = creatorSocket->read(buf, toRead); - if (rRead == -1) { - errorMsg(creatorSocket->errorString()); - stopGdbServer(); - return; - } - if (rRead == 0) { - if (!creatorSocket->isOpen()) - stopGdbServer(); - return; - } - int pos = 0; - int irep = 0; - if (debugGdbEchoServer) { - writeMsg("sendToGdbServer:"); - buf[rRead] = 0; - writeMsg(buf); - } - while (true) { - qptrdiff written = write(gdbFileDescriptor, buf + pos, rRead); - if (written == -1) { - if (errno == EINTR) - continue; - if (errno == EAGAIN) { - if (++irep > 10) { - sleep(1); - irep = 0; - } - continue; - } - errorMsg(creatorSocket->errorString()); - stopGdbServer(); - return; - } - if (written == 0) { - stopGdbServer(); - return; - } - if (written < rRead) { - pos += written; - rRead -= written; - } else { - break; - } - } - } -} - -void IosTool::handleCreatorHasError(QAbstractSocket::SocketError error) -{ - errorMsg(tr("iOS Debugging connection to creator failed with error %1").arg(error)); - stopGdbServer(); -} - -bool IosTool::startServer() -{ - if (gdbFileDescriptor <= 0 || gdbServer.isListening() || creatorSocket != 0) - return false; - fcntl(gdbFileDescriptor, F_SETFL, fcntl(gdbFileDescriptor, F_GETFL, 0) | O_NONBLOCK); - gdbServer.setMaxPendingConnections(1); - connect(&gdbServer, SIGNAL(newConnection()), SLOT(handleNewConnection())); - if (ipv6) - return gdbServer.listen(QHostAddress(QHostAddress::LocalHostIPv6), 0); - else - return gdbServer.listen(QHostAddress(QHostAddress::LocalHost), 0); -} - - int main(int argc, char *argv[]) { + // We do not pass the real arguments to QCoreApplication because this wrapper needs to be able + // to forward arguments like -qmljsdebugger=... that are filtered by QCoreApplication + QStringList args; + for (int iarg = 0; iarg < argc ; ++iarg) + args << QString::fromLocal8Bit(argv[iarg]); + char *qtArg = 0; + int qtArgc = 0; + if (argc > 0) { + qtArg = argv[0]; + qtArgc = 1; + } + #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - QApplication a(argc, argv); + QApplication a(qtArgc, &qtArg); #else - QGuiApplication a(argc, argv); + QGuiApplication a(qtArgc, &qtArg); #endif IosTool tool; - tool.run(QCoreApplication::arguments()); + tool.run(args); int res = a.exec(); exit(res); } #include "main.moc" + + +GdbRunner::GdbRunner(IosTool *iosTool, int gdbFd) : + QObject(0), m_iosTool(iosTool), m_gdbFd(gdbFd) +{ +} + +void GdbRunner::run() +{ + m_iosTool->errorMsg(QString::fromLatin1("GdbRunner in thread %1").arg((quintptr)(void *)QThread::currentThread())); + { + QMutexLocker l(&m_iosTool->m_xmlMutex); + if (!m_iosTool->splitAppOutput) { + m_iosTool->out.writeStartElement(QLatin1String("app_output")); + m_iosTool->inAppOutput = true; + } + m_iosTool->outFile.flush(); + } + Ios::IosDeviceManager::instance()->processGdbServer(m_gdbFd); + { + QMutexLocker l(&m_iosTool->m_xmlMutex); + if (!m_iosTool->splitAppOutput) { + m_iosTool->inAppOutput = false; + m_iosTool->out.writeEndElement(); + } + m_iosTool->outFile.flush(); + } + close(m_gdbFd); + m_iosTool->doExit(); + emit finished(); +}