PageRenderTime 50ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/libs/utils/consoleprocess_win.cpp

https://bitbucket.org/kyanha/qt-creator
C++ | 371 lines | 300 code | 38 blank | 33 comment | 43 complexity | 5282eba0961e343a13de2861f0bb1a29 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
  4. ** Contact: http://www.qt-project.org/legal
  5. **
  6. ** This file is part of Qt Creator.
  7. **
  8. ** Commercial License Usage
  9. ** Licensees holding valid commercial Qt licenses may use this file in
  10. ** accordance with the commercial license agreement provided with the
  11. ** Software or, alternatively, in accordance with the terms contained in
  12. ** a written agreement between you and Digia. For licensing terms and
  13. ** conditions see http://qt.digia.com/licensing. For further information
  14. ** use the contact form at http://qt.digia.com/contact-us.
  15. **
  16. ** GNU Lesser General Public License Usage
  17. ** Alternatively, this file may be used under the terms of the GNU Lesser
  18. ** General Public License version 2.1 as published by the Free Software
  19. ** Foundation and appearing in the file LICENSE.LGPL included in the
  20. ** packaging of this file. Please review the following information to
  21. ** ensure the GNU Lesser General Public License version 2.1 requirements
  22. ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  23. **
  24. ** In addition, as a special exception, Digia gives you certain additional
  25. ** rights. These rights are described in the Digia Qt LGPL Exception
  26. ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  27. **
  28. ****************************************************************************/
  29. #include "consoleprocess_p.h"
  30. #include "environment.h"
  31. #include "qtcprocess.h"
  32. #include "winutils.h"
  33. #include <QCoreApplication>
  34. #include <QDir>
  35. #include <QAbstractEventDispatcher>
  36. #include <stdlib.h>
  37. namespace Utils {
  38. ConsoleProcessPrivate::ConsoleProcessPrivate() :
  39. m_mode(ConsoleProcess::Run),
  40. m_appPid(0),
  41. m_stubSocket(0),
  42. m_tempFile(0),
  43. m_appMainThreadId(0),
  44. m_pid(0),
  45. m_hInferior(NULL),
  46. inferiorFinishedNotifier(0),
  47. processFinishedNotifier(0)
  48. {
  49. }
  50. ConsoleProcess::ConsoleProcess(QObject *parent) :
  51. QObject(parent), d(new ConsoleProcessPrivate)
  52. {
  53. connect(&d->m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable()));
  54. }
  55. qint64 ConsoleProcess::applicationMainThreadID() const
  56. {
  57. return d->m_appMainThreadId;
  58. }
  59. bool ConsoleProcess::start(const QString &program, const QString &args)
  60. {
  61. if (isRunning())
  62. return false;
  63. QString pcmd;
  64. QString pargs;
  65. if (d->m_mode != Run) { // The debugger engines already pre-process the arguments.
  66. pcmd = program;
  67. pargs = args;
  68. } else {
  69. QtcProcess::prepareCommand(program, args, &pcmd, &pargs, &d->m_environment, &d->m_workingDir);
  70. }
  71. const QString err = stubServerListen();
  72. if (!err.isEmpty()) {
  73. emit processError(msgCommChannelFailed(err));
  74. return false;
  75. }
  76. QStringList env = d->m_environment.toStringList();
  77. if (!env.isEmpty()) {
  78. d->m_tempFile = new QTemporaryFile();
  79. if (!d->m_tempFile->open()) {
  80. stubServerShutdown();
  81. emit processError(msgCannotCreateTempFile(d->m_tempFile->errorString()));
  82. delete d->m_tempFile;
  83. d->m_tempFile = 0;
  84. return false;
  85. }
  86. QTextStream out(d->m_tempFile);
  87. out.setCodec("UTF-16LE");
  88. out.setGenerateByteOrderMark(false);
  89. foreach (const QString &var, fixWinEnvironment(env))
  90. out << var << QChar(0);
  91. out << QChar(0);
  92. #if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
  93. out.flush();
  94. if (out.status() != QTextStream::Ok) {
  95. stubServerShutdown();
  96. emit processError(msgCannotWriteTempFile());
  97. delete d->m_tempFile;
  98. d->m_tempFile = 0;
  99. return false;
  100. }
  101. #endif
  102. }
  103. STARTUPINFO si;
  104. ZeroMemory(&si, sizeof(si));
  105. si.cb = sizeof(si);
  106. d->m_pid = new PROCESS_INFORMATION;
  107. ZeroMemory(d->m_pid, sizeof(PROCESS_INFORMATION));
  108. QString workDir = QDir::toNativeSeparators(workingDirectory());
  109. if (!workDir.isEmpty() && !workDir.endsWith(QLatin1Char('\\')))
  110. workDir.append(QLatin1Char('\\'));
  111. QStringList stubArgs;
  112. stubArgs << modeOption(d->m_mode)
  113. << d->m_stubServer.fullServerName()
  114. << workDir
  115. << (d->m_tempFile ? d->m_tempFile->fileName() : QString())
  116. << createWinCommandline(pcmd, pargs)
  117. << msgPromptToClose();
  118. const QString cmdLine = createWinCommandline(
  119. QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_process_stub.exe"), stubArgs);
  120. bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(),
  121. 0, 0, FALSE, CREATE_NEW_CONSOLE,
  122. 0, 0,
  123. &si, d->m_pid);
  124. if (!success) {
  125. delete d->m_pid;
  126. d->m_pid = 0;
  127. delete d->m_tempFile;
  128. d->m_tempFile = 0;
  129. stubServerShutdown();
  130. emit processError(tr("The process '%1' could not be started: %2").arg(cmdLine, winErrorMessage(GetLastError())));
  131. return false;
  132. }
  133. d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this);
  134. connect(d->processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited()));
  135. emit wrapperStarted();
  136. return true;
  137. }
  138. void ConsoleProcess::stop()
  139. {
  140. if (d->m_hInferior != NULL) {
  141. TerminateProcess(d->m_hInferior, (unsigned)-1);
  142. cleanupInferior();
  143. }
  144. if (d->m_pid) {
  145. TerminateProcess(d->m_pid->hProcess, (unsigned)-1);
  146. WaitForSingleObject(d->m_pid->hProcess, INFINITE);
  147. cleanupStub();
  148. }
  149. }
  150. bool ConsoleProcess::isRunning() const
  151. {
  152. return d->m_pid != 0;
  153. }
  154. QString ConsoleProcess::stubServerListen()
  155. {
  156. if (d->m_stubServer.listen(QString::fromLatin1("creator-%1-%2")
  157. .arg(QCoreApplication::applicationPid())
  158. .arg(rand())))
  159. return QString();
  160. return d->m_stubServer.errorString();
  161. }
  162. void ConsoleProcess::stubServerShutdown()
  163. {
  164. delete d->m_stubSocket;
  165. d->m_stubSocket = 0;
  166. if (d->m_stubServer.isListening())
  167. d->m_stubServer.close();
  168. }
  169. void ConsoleProcess::stubConnectionAvailable()
  170. {
  171. d->m_stubSocket = d->m_stubServer.nextPendingConnection();
  172. connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput()));
  173. }
  174. void ConsoleProcess::readStubOutput()
  175. {
  176. while (d->m_stubSocket->canReadLine()) {
  177. QByteArray out = d->m_stubSocket->readLine();
  178. out.chop(2); // \r\n
  179. if (out.startsWith("err:chdir ")) {
  180. emit processError(msgCannotChangeToWorkDir(workingDirectory(), winErrorMessage(out.mid(10).toInt())));
  181. } else if (out.startsWith("err:exec ")) {
  182. emit processError(msgCannotExecute(d->m_executable, winErrorMessage(out.mid(9).toInt())));
  183. } else if (out.startsWith("thread ")) { // Windows only
  184. d->m_appMainThreadId = out.mid(7).toLongLong();
  185. } else if (out.startsWith("pid ")) {
  186. // Will not need it any more
  187. delete d->m_tempFile;
  188. d->m_tempFile = 0;
  189. d->m_appPid = out.mid(4).toLongLong();
  190. d->m_hInferior = OpenProcess(
  191. SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE,
  192. FALSE, d->m_appPid);
  193. if (d->m_hInferior == NULL) {
  194. emit processError(tr("Cannot obtain a handle to the inferior: %1")
  195. .arg(winErrorMessage(GetLastError())));
  196. // Uhm, and now what?
  197. continue;
  198. }
  199. d->inferiorFinishedNotifier = new QWinEventNotifier(d->m_hInferior, this);
  200. connect(d->inferiorFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(inferiorExited()));
  201. emit processStarted();
  202. } else {
  203. emit processError(msgUnexpectedOutput(out));
  204. TerminateProcess(d->m_pid->hProcess, (unsigned)-1);
  205. break;
  206. }
  207. }
  208. }
  209. void ConsoleProcess::cleanupInferior()
  210. {
  211. delete d->inferiorFinishedNotifier;
  212. d->inferiorFinishedNotifier = 0;
  213. CloseHandle(d->m_hInferior);
  214. d->m_hInferior = NULL;
  215. d->m_appPid = 0;
  216. }
  217. void ConsoleProcess::inferiorExited()
  218. {
  219. DWORD chldStatus;
  220. if (!GetExitCodeProcess(d->m_hInferior, &chldStatus))
  221. emit processError(tr("Cannot obtain exit status from inferior: %1")
  222. .arg(winErrorMessage(GetLastError())));
  223. cleanupInferior();
  224. d->m_appStatus = QProcess::NormalExit;
  225. d->m_appCode = chldStatus;
  226. emit processStopped();
  227. }
  228. void ConsoleProcess::cleanupStub()
  229. {
  230. stubServerShutdown();
  231. delete d->processFinishedNotifier;
  232. d->processFinishedNotifier = 0;
  233. CloseHandle(d->m_pid->hThread);
  234. CloseHandle(d->m_pid->hProcess);
  235. delete d->m_pid;
  236. d->m_pid = 0;
  237. delete d->m_tempFile;
  238. d->m_tempFile = 0;
  239. }
  240. void ConsoleProcess::stubExited()
  241. {
  242. // The stub exit might get noticed before we read the pid for the kill.
  243. if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState)
  244. d->m_stubSocket->waitForDisconnected();
  245. cleanupStub();
  246. if (d->m_hInferior != NULL) {
  247. TerminateProcess(d->m_hInferior, (unsigned)-1);
  248. cleanupInferior();
  249. d->m_appStatus = QProcess::CrashExit;
  250. d->m_appCode = -1;
  251. emit processStopped();
  252. }
  253. emit wrapperStopped();
  254. }
  255. QStringList ConsoleProcess::fixWinEnvironment(const QStringList &env)
  256. {
  257. QStringList envStrings = env;
  258. // add PATH if necessary (for DLL loading)
  259. if (envStrings.filter(QRegExp(QLatin1String("^PATH="),Qt::CaseInsensitive)).isEmpty()) {
  260. QByteArray path = qgetenv("PATH");
  261. if (!path.isEmpty())
  262. envStrings.prepend(QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path)));
  263. }
  264. // add systemroot if needed
  265. if (envStrings.filter(QRegExp(QLatin1String("^SystemRoot="),Qt::CaseInsensitive)).isEmpty()) {
  266. QByteArray systemRoot = qgetenv("SystemRoot");
  267. if (!systemRoot.isEmpty())
  268. envStrings.prepend(QString(QLatin1String("SystemRoot=%1")).arg(QString::fromLocal8Bit(systemRoot)));
  269. }
  270. return envStrings;
  271. }
  272. static QString quoteWinCommand(const QString &program)
  273. {
  274. const QChar doubleQuote = QLatin1Char('"');
  275. // add the program as the first arg ... it works better
  276. QString programName = program;
  277. programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
  278. if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote)
  279. && programName.contains(QLatin1Char(' '))) {
  280. programName.prepend(doubleQuote);
  281. programName.append(doubleQuote);
  282. }
  283. return programName;
  284. }
  285. static QString quoteWinArgument(const QString &arg)
  286. {
  287. if (!arg.length())
  288. return QString::fromLatin1("\"\"");
  289. QString ret(arg);
  290. // Quotes are escaped and their preceding backslashes are doubled.
  291. ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\""));
  292. if (ret.contains(QRegExp(QLatin1String("\\s")))) {
  293. // The argument must not end with a \ since this would be interpreted
  294. // as escaping the quote -- rather put the \ behind the quote: e.g.
  295. // rather use "foo"\ than "foo\"
  296. int i = ret.length();
  297. while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
  298. --i;
  299. ret.insert(i, QLatin1Char('"'));
  300. ret.prepend(QLatin1Char('"'));
  301. }
  302. return ret;
  303. }
  304. QString ConsoleProcess::createWinCommandline(const QString &program, const QStringList &args)
  305. {
  306. QString programName = quoteWinCommand(program);
  307. foreach (const QString &arg, args) {
  308. programName += QLatin1Char(' ');
  309. programName += quoteWinArgument(arg);
  310. }
  311. return programName;
  312. }
  313. QString ConsoleProcess::createWinCommandline(const QString &program, const QString &args)
  314. {
  315. QString programName = quoteWinCommand(program);
  316. if (!args.isEmpty()) {
  317. programName += QLatin1Char(' ');
  318. programName += args;
  319. }
  320. return programName;
  321. }
  322. QString ConsoleProcess::defaultTerminalEmulator()
  323. {
  324. return QString::fromLocal8Bit(qgetenv("COMSPEC"));
  325. }
  326. QStringList ConsoleProcess::availableTerminalEmulators()
  327. {
  328. return QStringList(ConsoleProcess::defaultTerminalEmulator());
  329. }
  330. } // namespace Utils