1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
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 The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "qtcprocess.h"
27
28 #include "stringutils.h"
29 #include "executeondestruction.h"
30 #include "hostosinfo.h"
31 #include "commandline.h"
32 #include "qtcassert.h"
33
34 #include <QCoreApplication>
35 #include <QDebug>
36 #include <QDir>
37 #include <QLoggingCategory>
38 #include <QTextCodec>
39 #include <QThread>
40 #include <QTimer>
41
42 #ifdef QT_GUI_LIB
43 // qmlpuppet does not use that.
44 #include <QApplication>
45 #include <QMessageBox>
46 #endif
47
48 #include <algorithm>
49 #include <limits.h>
50 #include <memory>
51
52 #ifdef Q_OS_WIN
53 #ifdef QTCREATOR_PCH_H
54 #define CALLBACK WINAPI
55 #endif
56 #include <qt_windows.h>
57 #else
58 #include <errno.h>
59 #include <stdio.h>
60 #include <unistd.h>
61 #endif
62
63
64 using namespace Utils::Internal;
65
66 namespace Utils {
67 namespace Internal {
68
69 enum { debug = 0 };
70 enum { syncDebug = 0 };
71
72 enum { defaultMaxHangTimerCount = 10 };
73
74 static Q_LOGGING_CATEGORY(processLog, "qtc.utils.qtcprocess", QtWarningMsg)
75
76 static DeviceProcessHooks s_deviceHooks;
77
78 // Data for one channel buffer (stderr/stdout)
79 class ChannelBuffer
80 {
81 public:
82 void clearForRun();
83
84 void handleRest();
85 void append(const QByteArray &text);
86
87 QByteArray rawData;
88 QString incompleteLineBuffer; // lines not yet signaled
89 QTextCodec *codec = nullptr; // Not owner
90 std::unique_ptr<QTextCodec::ConverterState> codecState;
91 std::function<void(const QString &lines)> outputCallback;
92
93 bool emitSingleLines = true;
94 bool keepRawData = true;
95 };
96
97 class ProcessHelper : public QProcess
98 {
99 public:
ProcessHelper()100 ProcessHelper()
101 {
102 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) && defined(Q_OS_UNIX)
103 setChildProcessModifier([this] { setupChildProcess_impl(); });
104 #endif
105 }
106
107 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
setupChildProcess()108 void setupChildProcess() override { setupChildProcess_impl(); }
109 #endif
110
setupChildProcess_impl()111 void setupChildProcess_impl()
112 {
113 #if defined Q_OS_UNIX
114 // nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest
115 if (m_lowPriority) {
116 errno = 0;
117 if (::nice(5) == -1 && errno != 0)
118 perror("Failed to set nice value");
119 }
120
121 // Disable terminal by becoming a session leader.
122 if (m_disableUnixTerminal)
123 setsid();
124 #endif
125 }
126
127 using QProcess::setErrorString;
128
129 bool m_lowPriority = false;
130 bool m_disableUnixTerminal = false;
131 bool m_keepStdInOpen = false;
132 };
133
134 class QtcProcessPrivate : public QObject
135 {
136 public:
QtcProcessPrivate(QtcProcess * parent)137 explicit QtcProcessPrivate(QtcProcess *parent)
138 : q(parent), m_process(new ProcessHelper)
139 {
140 connect(m_process, &QProcess::started,
141 q, &QtcProcess::started);
142 connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
143 this, &QtcProcessPrivate::slotFinished);
144 connect(m_process, &QProcess::errorOccurred,
145 this, &QtcProcessPrivate::slotError);
146 connect(m_process, &QProcess::stateChanged,
147 q, &QtcProcess::stateChanged);
148 connect(m_process, &QProcess::readyReadStandardOutput,
149 this, &QtcProcessPrivate::handleReadyReadStandardOutput);
150 connect(m_process, &QProcess::readyReadStandardError,
151 this, &QtcProcessPrivate::handleReadyReadStandardError);
152 connect(&m_timer, &QTimer::timeout, this, &QtcProcessPrivate::slotTimeout);
153 m_timer.setInterval(1000);
154 }
155
~QtcProcessPrivate()156 ~QtcProcessPrivate()
157 {
158 delete m_process;
159 }
160
handleReadyReadStandardOutput()161 void handleReadyReadStandardOutput()
162 {
163 m_stdOut.append(m_process->readAllStandardOutput());
164 m_hangTimerCount = 0;
165 emit q->readyReadStandardOutput();
166 }
167
handleReadyReadStandardError()168 void handleReadyReadStandardError()
169 {
170 m_stdErr.append(m_process->readAllStandardError());
171 m_hangTimerCount = 0;
172 emit q->readyReadStandardError();
173 }
174
175 QtcProcess *q;
176 ProcessHelper *m_process;
177 CommandLine m_commandLine;
178 FilePath m_workingDirectory;
179 Environment m_environment;
180 QByteArray m_writeData;
181 bool m_haveEnv = false;
182 bool m_useCtrlCStub = false;
183
184 QProcess::OpenMode m_openMode = QProcess::ReadWrite;
185
186 void slotTimeout();
187 void slotFinished(int exitCode, QProcess::ExitStatus e);
188 void slotError(QProcess::ProcessError);
189 void clearForRun();
190
191 QtcProcess::Result interpretExitCode(int exitCode);
192
193 QTextCodec *m_codec = QTextCodec::codecForLocale();
194 QTimer m_timer;
195 QEventLoop m_eventLoop;
196 QtcProcess::Result m_result = QtcProcess::StartFailed;
197 QProcess::ExitStatus m_exitStatus = QProcess::NormalExit;
198 int m_exitCode = -1;
199 ChannelBuffer m_stdOut;
200 ChannelBuffer m_stdErr;
201 ExitCodeInterpreter m_exitCodeInterpreter;
202
203 int m_hangTimerCount = 0;
204 int m_maxHangTimerCount = defaultMaxHangTimerCount;
205 bool m_startFailure = false;
206 bool m_timeOutMessageBoxEnabled = false;
207 bool m_waitingForUser = false;
208 bool m_processUserEvents = false;
209 };
210
clearForRun()211 void QtcProcessPrivate::clearForRun()
212 {
213 m_hangTimerCount = 0;
214 m_stdOut.clearForRun();
215 m_stdOut.codec = m_codec;
216 m_stdErr.clearForRun();
217 m_stdErr.codec = m_codec;
218 m_result = QtcProcess::StartFailed;
219 m_exitCode = -1;
220 m_startFailure = false;
221 }
222
interpretExitCode(int exitCode)223 QtcProcess::Result QtcProcessPrivate::interpretExitCode(int exitCode)
224 {
225 if (m_exitCodeInterpreter)
226 return m_exitCodeInterpreter(exitCode);
227
228 // default:
229 return exitCode ? QtcProcess::FinishedWithError : QtcProcess::FinishedWithSuccess;
230 }
231
232 } // Internal
233
234 /*!
235 \class Utils::QtcProcess
236
237 \brief The QtcProcess class provides functionality for with processes.
238
239 \sa Utils::ProcessArgs
240 */
241
QtcProcess(QObject * parent)242 QtcProcess::QtcProcess(QObject *parent)
243 : QObject(parent), d(new QtcProcessPrivate(this))
244 {
245 static int qProcessExitStatusMeta = qRegisterMetaType<QProcess::ExitStatus>();
246 static int qProcessProcessErrorMeta = qRegisterMetaType<QProcess::ProcessError>();
247 Q_UNUSED(qProcessExitStatusMeta)
248 Q_UNUSED(qProcessProcessErrorMeta)
249 }
250
~QtcProcess()251 QtcProcess::~QtcProcess()
252 {
253 delete d;
254 }
255
setEnvironment(const Environment & env)256 void QtcProcess::setEnvironment(const Environment &env)
257 {
258 d->m_environment = env;
259 d->m_haveEnv = true;
260 }
261
unsetEnvironment()262 void QtcProcess::unsetEnvironment()
263 {
264 d->m_environment = Environment();
265 d->m_haveEnv = false;
266 }
267
environment() const268 const Environment &QtcProcess::environment() const
269 {
270 return d->m_environment;
271 }
272
setCommand(const CommandLine & cmdLine)273 void QtcProcess::setCommand(const CommandLine &cmdLine)
274 {
275 if (d->m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) {
276 QTC_CHECK(d->m_workingDirectory.host() == cmdLine.executable().host());
277 }
278 d->m_commandLine = cmdLine;
279 }
280
commandLine() const281 const CommandLine &QtcProcess::commandLine() const
282 {
283 return d->m_commandLine;
284 }
285
workingDirectory() const286 FilePath QtcProcess::workingDirectory() const
287 {
288 return d->m_workingDirectory;
289 }
290
setWorkingDirectory(const FilePath & dir)291 void QtcProcess::setWorkingDirectory(const FilePath &dir)
292 {
293 if (dir.needsDevice() && d->m_commandLine.executable().needsDevice()) {
294 QTC_CHECK(dir.host() == d->m_commandLine.executable().host());
295 }
296 d->m_workingDirectory = dir;
297 }
298
setWorkingDirectory(const QString & dir)299 void QtcProcess::setWorkingDirectory(const QString &dir)
300 {
301 setWorkingDirectory(FilePath::fromString(dir));
302 }
303
setUseCtrlCStub(bool enabled)304 void QtcProcess::setUseCtrlCStub(bool enabled)
305 {
306 // Do not use the stub in debug mode. Activating the stub will shut down
307 // Qt Creator otherwise, because they share the same Windows console.
308 // See QTCREATORBUG-11995 for details.
309 #ifndef QT_DEBUG
310 d->m_useCtrlCStub = enabled;
311 #else
312 Q_UNUSED(enabled)
313 #endif
314 }
315
start(const QString & cmd,const QStringList & args)316 void QtcProcess::start(const QString &cmd, const QStringList &args)
317 {
318 setCommand({cmd, args});
319 start();
320 }
321
start()322 void QtcProcess::start()
323 {
324 d->clearForRun();
325
326 if (!d->m_writeData.isEmpty()) {
327 connect(d->m_process, &QProcess::started, this, [this] {
328 const qint64 bytesWritten = write(d->m_writeData);
329 QTC_CHECK(bytesWritten == d->m_writeData.size());
330 d->m_process->waitForBytesWritten();
331 closeWriteChannel(); // FIXME: Is this good?
332 });
333 }
334
335 if (d->m_commandLine.executable().needsDevice()) {
336 QTC_ASSERT(s_deviceHooks.startProcessHook, return);
337 s_deviceHooks.startProcessHook(*this);
338 return;
339 }
340
341 if (processLog().isDebugEnabled()) {
342 static int n = 0;
343 qCDebug(processLog) << "STARTING PROCESS: " << ++n << " " << d->m_commandLine.toUserOutput();
344 }
345
346 Environment env;
347 const OsType osType = HostOsInfo::hostOs();
348 if (d->m_haveEnv) {
349 if (d->m_environment.size() == 0)
350 qWarning("QtcProcess::start: Empty environment set when running '%s'.",
351 qPrintable(d->m_commandLine.executable().toString()));
352 env = d->m_environment;
353 } else {
354 env = Environment::systemEnvironment();
355 }
356 d->m_process->setProcessEnvironment(env.toProcessEnvironment());
357
358 const QString workDir = d->m_workingDirectory.path();
359 d->m_process->setWorkingDirectory(workDir);
360
361 QString command;
362 ProcessArgs arguments;
363 bool success = ProcessArgs::prepareCommand(d->m_commandLine.executable().toString(),
364 d->m_commandLine.arguments(),
365 &command, &arguments, osType, &env, &workDir);
366 if (osType == OsTypeWindows) {
367 QString args;
368 if (d->m_useCtrlCStub) {
369 if (d->m_process->m_lowPriority)
370 ProcessArgs::addArg(&args, "-nice");
371 ProcessArgs::addArg(&args, QDir::toNativeSeparators(command));
372 command = QCoreApplication::applicationDirPath()
373 + QLatin1String("/qtcreator_ctrlc_stub.exe");
374 } else if (d->m_process->m_lowPriority) {
375 #ifdef Q_OS_WIN
376 d->m_process->setCreateProcessArgumentsModifier(
377 [](QProcess::CreateProcessArguments *args) {
378 args->flags |= BELOW_NORMAL_PRIORITY_CLASS;
379 });
380 #endif
381 }
382 ProcessArgs::addArgs(&args, arguments.toWindowsArgs());
383 #ifdef Q_OS_WIN
384 d->m_process->setNativeArguments(args);
385 #endif
386 // Note: Arguments set with setNativeArgs will be appended to the ones
387 // passed with start() below.
388 d->m_process->start(command, QStringList(), d->m_openMode);
389 } else {
390 if (!success) {
391 setErrorString(tr("Error in command line."));
392 // Should be FailedToStart, but we cannot set the process error from the outside,
393 // so it would be inconsistent.
394 emit errorOccurred(QProcess::UnknownError);
395 return;
396 }
397 d->m_process->start(command, arguments.toUnixArgs(), d->m_openMode);
398 }
399 }
400
401 #ifdef Q_OS_WIN
sendMessage(UINT message,HWND hwnd,LPARAM lParam)402 static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam)
403 {
404 DWORD dwProcessID;
405 GetWindowThreadProcessId(hwnd, &dwProcessID);
406 if ((DWORD)lParam == dwProcessID) {
407 SendNotifyMessage(hwnd, message, 0, 0);
408 return FALSE;
409 }
410 return TRUE;
411 }
412
sendShutDownMessageToAllWindowsOfProcess_enumWnd(HWND hwnd,LPARAM lParam)413 BOOL CALLBACK sendShutDownMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
414 {
415 static UINT uiShutDownMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown");
416 return sendMessage(uiShutDownMessage, hwnd, lParam);
417 }
418
sendInterruptMessageToAllWindowsOfProcess_enumWnd(HWND hwnd,LPARAM lParam)419 BOOL CALLBACK sendInterruptMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
420 {
421 static UINT uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt");
422 return sendMessage(uiInterruptMessage, hwnd, lParam);
423 }
424 #endif
425
terminate()426 void QtcProcess::terminate()
427 {
428 #ifdef Q_OS_WIN
429 if (d->m_useCtrlCStub)
430 EnumWindows(sendShutDownMessageToAllWindowsOfProcess_enumWnd, processId());
431 else
432 #endif
433 d->m_process->terminate();
434 }
435
interrupt()436 void QtcProcess::interrupt()
437 {
438 #ifdef Q_OS_WIN
439 QTC_ASSERT(d->m_useCtrlCStub, return);
440 EnumWindows(sendInterruptMessageToAllWindowsOfProcess_enumWnd, processId());
441 #endif
442 }
443
setLowPriority()444 void QtcProcess::setLowPriority()
445 {
446 d->m_process->m_lowPriority = true;
447 }
448
setDisableUnixTerminal()449 void QtcProcess::setDisableUnixTerminal()
450 {
451 d->m_process->m_disableUnixTerminal = true;
452 }
453
setKeepWriteChannelOpen()454 void QtcProcess::setKeepWriteChannelOpen()
455 {
456 d->m_process->m_keepStdInOpen = true;
457 }
458
keepsWriteChannelOpen() const459 bool QtcProcess::keepsWriteChannelOpen() const
460 {
461 return d->m_process->m_keepStdInOpen;
462 }
463
setStandardInputFile(const QString & inputFile)464 void QtcProcess::setStandardInputFile(const QString &inputFile)
465 {
466 d->m_process->setStandardInputFile(inputFile);
467 }
468
setRemoteProcessHooks(const DeviceProcessHooks & hooks)469 void QtcProcess::setRemoteProcessHooks(const DeviceProcessHooks &hooks)
470 {
471 s_deviceHooks = hooks;
472 }
473
setOpenMode(QIODevice::OpenMode mode)474 void QtcProcess::setOpenMode(QIODevice::OpenMode mode)
475 {
476 d->m_openMode = mode;
477 }
478
stopProcess()479 bool QtcProcess::stopProcess()
480 {
481 if (state() == QProcess::NotRunning)
482 return true;
483 terminate();
484 if (waitForFinished(300))
485 return true;
486 kill();
487 return waitForFinished(300);
488 }
489
askToKill(const QString & command)490 static bool askToKill(const QString &command)
491 {
492 #ifdef QT_GUI_LIB
493 if (QThread::currentThread() != QCoreApplication::instance()->thread())
494 return true;
495 const QString title = QtcProcess::tr("Process not Responding");
496 QString msg = command.isEmpty() ?
497 QtcProcess::tr("The process is not responding.") :
498 QtcProcess::tr("The process \"%1\" is not responding.").arg(command);
499 msg += ' ';
500 msg += QtcProcess::tr("Would you like to terminate it?");
501 // Restore the cursor that is set to wait while running.
502 const bool hasOverrideCursor = QApplication::overrideCursor() != nullptr;
503 if (hasOverrideCursor)
504 QApplication::restoreOverrideCursor();
505 QMessageBox::StandardButton answer = QMessageBox::question(nullptr, title, msg, QMessageBox::Yes|QMessageBox::No);
506 if (hasOverrideCursor)
507 QApplication::setOverrideCursor(Qt::WaitCursor);
508 return answer == QMessageBox::Yes;
509 #else
510 Q_UNUSED(command)
511 return true;
512 #endif
513 }
514
515 // Helper for running a process synchronously in the foreground with timeout
516 // detection (taking effect after no more output
517 // occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout
518 // occurs. Checking of the process' exit state/code still has to be done.
519
readDataFromProcess(int timeoutS,QByteArray * stdOut,QByteArray * stdErr,bool showTimeOutMessageBox)520 bool QtcProcess::readDataFromProcess(int timeoutS,
521 QByteArray *stdOut,
522 QByteArray *stdErr,
523 bool showTimeOutMessageBox)
524 {
525 enum { syncDebug = 0 };
526 if (syncDebug)
527 qDebug() << ">readDataFromProcess" << timeoutS;
528 if (state() != QProcess::Running) {
529 qWarning("readDataFromProcess: Process in non-running state passed in.");
530 return false;
531 }
532
533 QTC_ASSERT(d->m_process->readChannel() == QProcess::StandardOutput, return false);
534
535 // Keep the process running until it has no longer has data
536 bool finished = false;
537 bool hasData = false;
538 do {
539 finished = waitForFinished(timeoutS > 0 ? timeoutS * 1000 : -1)
540 || state() == QProcess::NotRunning;
541 // First check 'stdout'
542 const QByteArray newStdOut = readAllStandardOutput();
543 if (!newStdOut.isEmpty()) {
544 hasData = true;
545 if (stdOut)
546 stdOut->append(newStdOut);
547 }
548 // Check 'stderr' separately. This is a special handling
549 // for 'git pull' and the like which prints its progress on stderr.
550 const QByteArray newStdErr = readAllStandardError();
551 if (!newStdErr.isEmpty()) {
552 hasData = true;
553 if (stdErr)
554 stdErr->append(newStdErr);
555 }
556 // Prompt user, pretend we have data if says 'No'.
557 const bool hang = !hasData && !finished;
558 hasData = hang && showTimeOutMessageBox && !askToKill(d->m_process->program());
559 } while (hasData && !finished);
560 if (syncDebug)
561 qDebug() << "<readDataFromProcess" << finished;
562 return finished;
563 }
564
normalizeNewlines(const QString & text)565 QString QtcProcess::normalizeNewlines(const QString &text)
566 {
567 QString res = text;
568 const auto newEnd = std::unique(res.begin(), res.end(), [](const QChar &c1, const QChar &c2) {
569 return c1 == '\r' && c2 == '\r'; // QTCREATORBUG-24556
570 });
571 res.chop(std::distance(newEnd, res.end()));
572 res.replace("\r\n", "\n");
573 return res;
574 }
575
result() const576 QtcProcess::Result QtcProcess::result() const
577 {
578 return d->m_result;
579 }
580
setResult(Result result)581 void QtcProcess::setResult(Result result)
582 {
583 d->m_result = result;
584 }
585
exitCode() const586 int QtcProcess::exitCode() const
587 {
588 return d->m_exitCode;
589 }
590
591
592 // Path utilities
593
594 // Locate a binary in a directory, applying all kinds of
595 // extensions the operating system supports.
checkBinary(const QDir & dir,const QString & binary)596 static QString checkBinary(const QDir &dir, const QString &binary)
597 {
598 // naive UNIX approach
599 const QFileInfo info(dir.filePath(binary));
600 if (info.isFile() && info.isExecutable())
601 return info.absoluteFilePath();
602
603 // Does the OS have some weird extension concept or does the
604 // binary have a 3 letter extension?
605 if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
606 return QString();
607 const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
608 if (dotIndex != -1 && dotIndex == binary.size() - 4)
609 return QString();
610
611 switch (HostOsInfo::hostOs()) {
612 case OsTypeLinux:
613 case OsTypeOtherUnix:
614 case OsTypeOther:
615 break;
616 case OsTypeWindows: {
617 static const char *windowsExtensions[] = {".cmd", ".bat", ".exe", ".com"};
618 // Check the Windows extensions using the order
619 const int windowsExtensionCount = sizeof(windowsExtensions)/sizeof(const char*);
620 for (int e = 0; e < windowsExtensionCount; e ++) {
621 const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e])));
622 if (windowsBinary.isFile() && windowsBinary.isExecutable())
623 return windowsBinary.absoluteFilePath();
624 }
625 }
626 break;
627 case OsTypeMac: {
628 // Check for Mac app folders
629 const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app")));
630 if (appFolder.isDir()) {
631 QString macBinaryPath = appFolder.absoluteFilePath();
632 macBinaryPath += QLatin1String("/Contents/MacOS/");
633 macBinaryPath += binary;
634 const QFileInfo macBinary(macBinaryPath);
635 if (macBinary.isFile() && macBinary.isExecutable())
636 return macBinary.absoluteFilePath();
637 }
638 }
639 break;
640 }
641 return QString();
642 }
643
locateBinary(const QString & path,const QString & binary)644 QString QtcProcess::locateBinary(const QString &path, const QString &binary)
645 {
646 // Absolute file?
647 const QFileInfo absInfo(binary);
648 if (absInfo.isAbsolute())
649 return checkBinary(absInfo.dir(), absInfo.fileName());
650
651 // Windows finds binaries in the current directory
652 if (HostOsInfo::isWindowsHost()) {
653 const QString currentDirBinary = checkBinary(QDir::current(), binary);
654 if (!currentDirBinary.isEmpty())
655 return currentDirBinary;
656 }
657
658 const QStringList paths = path.split(HostOsInfo::pathListSeparator());
659 if (paths.empty())
660 return QString();
661 const QStringList::const_iterator cend = paths.constEnd();
662 for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) {
663 const QDir dir(*it);
664 const QString rc = checkBinary(dir, binary);
665 if (!rc.isEmpty())
666 return rc;
667 }
668 return QString();
669 }
670
systemEnvironmentForBinary(const FilePath & filePath)671 Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath)
672 {
673 if (filePath.needsDevice()) {
674 QTC_ASSERT(s_deviceHooks.systemEnvironmentForBinary, return {});
675 return s_deviceHooks.systemEnvironmentForBinary(filePath);
676 }
677
678 return Environment::systemEnvironment();
679 }
680
setProcessChannelMode(QProcess::ProcessChannelMode mode)681 void QtcProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
682 {
683 d->m_process->setProcessChannelMode(mode);
684 }
685
error() const686 QProcess::ProcessError QtcProcess::error() const
687 {
688 return d->m_process->error();
689 }
690
state() const691 QProcess::ProcessState QtcProcess::state() const
692 {
693 return d->m_process->state();
694 }
695
errorString() const696 QString QtcProcess::errorString() const
697 {
698 return d->m_process->errorString();
699 }
700
setErrorString(const QString & str)701 void QtcProcess::setErrorString(const QString &str)
702 {
703 d->m_process->setErrorString(str);
704 }
705
processId() const706 qint64 QtcProcess::processId() const
707 {
708 return d->m_process->processId();
709 }
710
waitForStarted(int msecs)711 bool QtcProcess::waitForStarted(int msecs)
712 {
713 return d->m_process->waitForStarted(msecs);
714 }
715
waitForReadyRead(int msecs)716 bool QtcProcess::waitForReadyRead(int msecs)
717 {
718 return d->m_process->waitForReadyRead(msecs);
719 }
720
waitForFinished(int msecs)721 bool QtcProcess::waitForFinished(int msecs)
722 {
723 return d->m_process->waitForFinished(msecs);
724 }
725
readAllStandardOutput()726 QByteArray QtcProcess::readAllStandardOutput()
727 {
728 QByteArray buf = d->m_stdOut.rawData;
729 d->m_stdOut.rawData.clear();
730 return buf;
731 }
732
readAllStandardError()733 QByteArray QtcProcess::readAllStandardError()
734 {
735 QByteArray buf = d->m_stdErr.rawData;
736 d->m_stdErr.rawData.clear();
737 return buf;
738 }
739
exitStatus() const740 QProcess::ExitStatus QtcProcess::exitStatus() const
741 {
742 return d->m_process->exitStatus();
743 }
744
kill()745 void QtcProcess::kill()
746 {
747 d->m_process->kill();
748 }
749
write(const QByteArray & input)750 qint64 QtcProcess::write(const QByteArray &input)
751 {
752 return d->m_process->write(input);
753 }
754
closeWriteChannel()755 void QtcProcess::closeWriteChannel()
756 {
757 d->m_process->closeWriteChannel();
758 }
759
close()760 void QtcProcess::close()
761 {
762 d->m_process->close();
763 }
764
beginFeed()765 void QtcProcess::beginFeed()
766 {
767 d->clearForRun();
768 }
769
endFeed()770 void QtcProcess::endFeed()
771 {
772 d->slotFinished(0, QProcess::NormalExit);
773 }
774
feedStdOut(const QByteArray & data)775 void QtcProcess::feedStdOut(const QByteArray &data)
776 {
777 d->m_stdOut.append(data);
778 d->m_hangTimerCount = 0;
779 emit readyReadStandardOutput();
780 }
781
locateBinary(const QString & binary)782 QString QtcProcess::locateBinary(const QString &binary)
783 {
784 const QByteArray path = qgetenv("PATH");
785 return locateBinary(QString::fromLocal8Bit(path), binary);
786 }
787
788
789 /*!
790 \class Utils::SynchronousProcess
791
792 \brief The SynchronousProcess class runs a synchronous process in its own
793 event loop that blocks only user input events. Thus, it allows for the GUI to
794 repaint and append output to log windows.
795
796 The callbacks set with setStdOutCallback(), setStdErrCallback() are called
797 with complete lines based on the '\\n' marker.
798 They would typically be used for log windows.
799
800 Alternatively you can used setStdOutLineCallback() and setStdErrLineCallback()
801 to process the output line by line.
802
803 There is a timeout handling that takes effect after the last data have been
804 read from stdout/stdin (as opposed to waitForFinished(), which measures time
805 since it was invoked). It is thus also suitable for slow processes that
806 continuously output data (like version system operations).
807
808 The property timeOutMessageBoxEnabled influences whether a message box is
809 shown asking the user if they want to kill the process on timeout (default: false).
810
811 There are also static utility functions for dealing with fully synchronous
812 processes, like reading the output with correct timeout handling.
813
814 Caution: This class should NOT be used if there is a chance that the process
815 triggers opening dialog boxes (for example, by file watchers triggering),
816 as this will cause event loop problems.
817 */
818
exitMessage()819 QString QtcProcess::exitMessage()
820 {
821 const QString fullCmd = commandLine().toUserOutput();
822 switch (result()) {
823 case FinishedWithSuccess:
824 return QtcProcess::tr("The command \"%1\" finished successfully.").arg(fullCmd);
825 case FinishedWithError:
826 return QtcProcess::tr("The command \"%1\" terminated with exit code %2.")
827 .arg(fullCmd).arg(exitCode());
828 case TerminatedAbnormally:
829 return QtcProcess::tr("The command \"%1\" terminated abnormally.").arg(fullCmd);
830 case StartFailed:
831 return QtcProcess::tr("The command \"%1\" could not be started.").arg(fullCmd);
832 case Hang:
833 return QtcProcess::tr("The command \"%1\" did not respond within the timeout limit (%2 s).")
834 .arg(fullCmd).arg(d->m_maxHangTimerCount);
835 }
836 return QString();
837 }
838
allRawOutput() const839 QByteArray QtcProcess::allRawOutput() const
840 {
841 QTC_CHECK(d->m_stdOut.keepRawData);
842 QTC_CHECK(d->m_stdErr.keepRawData);
843 if (!d->m_stdOut.rawData.isEmpty() && !d->m_stdErr.rawData.isEmpty()) {
844 QByteArray result = d->m_stdOut.rawData;
845 if (!result.endsWith('\n'))
846 result += '\n';
847 result += d->m_stdErr.rawData;
848 return result;
849 }
850 return !d->m_stdOut.rawData.isEmpty() ? d->m_stdOut.rawData : d->m_stdErr.rawData;
851 }
852
allOutput() const853 QString QtcProcess::allOutput() const
854 {
855 QTC_CHECK(d->m_stdOut.keepRawData);
856 QTC_CHECK(d->m_stdErr.keepRawData);
857 const QString out = stdOut();
858 const QString err = stdErr();
859
860 if (!out.isEmpty() && !err.isEmpty()) {
861 QString result = out;
862 if (!result.endsWith('\n'))
863 result += '\n';
864 result += err;
865 return result;
866 }
867 return !out.isEmpty() ? out : err;
868 }
869
stdOut() const870 QString QtcProcess::stdOut() const
871 {
872 QTC_CHECK(d->m_stdOut.keepRawData);
873 return normalizeNewlines(d->m_codec->toUnicode(d->m_stdOut.rawData));
874 }
875
stdErr() const876 QString QtcProcess::stdErr() const
877 {
878 // FIXME: The tighter check below is actually good theoretically, but currently
879 // ShellCommand::runFullySynchronous triggers it and disentangling there
880 // is not trivial. So weaken it a bit for now.
881 //QTC_CHECK(d->m_stdErr.keepRawData);
882 QTC_CHECK(d->m_stdErr.keepRawData || d->m_stdErr.rawData.isEmpty());
883 return normalizeNewlines(d->m_codec->toUnicode(d->m_stdErr.rawData));
884 }
885
rawStdOut() const886 QByteArray QtcProcess::rawStdOut() const
887 {
888 QTC_CHECK(d->m_stdOut.keepRawData);
889 return d->m_stdOut.rawData;
890 }
891
operator <<(QDebug str,const QtcProcess & r)892 QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r)
893 {
894 QDebug nsp = str.nospace();
895 nsp << "QtcProcess: result="
896 << r.d->m_result << " ex=" << r.exitCode() << '\n'
897 << r.d->m_stdOut.rawData.size() << " bytes stdout, stderr=" << r.d->m_stdErr.rawData << '\n';
898 return str;
899 }
900
clearForRun()901 void ChannelBuffer::clearForRun()
902 {
903 rawData.clear();
904 codecState.reset(new QTextCodec::ConverterState);
905 incompleteLineBuffer.clear();
906 }
907
908 /* Check for complete lines read from the device and return them, moving the
909 * buffer position. */
append(const QByteArray & text)910 void ChannelBuffer::append(const QByteArray &text)
911 {
912 if (text.isEmpty())
913 return;
914
915 if (keepRawData)
916 rawData += text;
917
918 // Line-wise operation below:
919 if (!outputCallback)
920 return;
921
922 // Convert and append the new input to the buffer of incomplete lines
923 incompleteLineBuffer.append(codec->toUnicode(text.constData(), text.size(), codecState.get()));
924
925 do {
926 // Any completed lines in the incompleteLineBuffer?
927 int pos = -1;
928 if (emitSingleLines) {
929 const int posn = incompleteLineBuffer.indexOf('\n');
930 const int posr = incompleteLineBuffer.indexOf('\r');
931 if (posn != -1) {
932 if (posr != -1) {
933 if (posn == posr + 1)
934 pos = posn; // \r followed by \n -> line end, use the \n.
935 else
936 pos = qMin(posr, posn); // free floating \r and \n: Use the first one.
937 } else {
938 pos = posn;
939 }
940 } else {
941 pos = posr; // Make sure internal '\r' triggers a line output
942 }
943 } else {
944 pos = qMax(incompleteLineBuffer.lastIndexOf('\n'),
945 incompleteLineBuffer.lastIndexOf('\r'));
946 }
947
948 if (pos == -1)
949 break;
950
951 // Get completed lines and remove them from the incompleteLinesBuffer:
952 const QString line = QtcProcess::normalizeNewlines(incompleteLineBuffer.left(pos + 1));
953 incompleteLineBuffer = incompleteLineBuffer.mid(pos + 1);
954
955 QTC_ASSERT(outputCallback, return);
956 outputCallback(line);
957
958 if (!emitSingleLines)
959 break;
960 } while (true);
961 }
962
handleRest()963 void ChannelBuffer::handleRest()
964 {
965 if (outputCallback && !incompleteLineBuffer.isEmpty()) {
966 outputCallback(incompleteLineBuffer);
967 incompleteLineBuffer.clear();
968 }
969 }
970
setProcessUserEventWhileRunning()971 void QtcProcess::setProcessUserEventWhileRunning()
972 {
973 d->m_processUserEvents = true;
974 }
975
setTimeoutS(int timeoutS)976 void QtcProcess::setTimeoutS(int timeoutS)
977 {
978 if (timeoutS > 0)
979 d->m_maxHangTimerCount = qMax(2, timeoutS);
980 else
981 d->m_maxHangTimerCount = INT_MAX / 1000;
982 }
983
setCodec(QTextCodec * c)984 void QtcProcess::setCodec(QTextCodec *c)
985 {
986 QTC_ASSERT(c, return);
987 d->m_codec = c;
988 }
989
setTimeOutMessageBoxEnabled(bool v)990 void QtcProcess::setTimeOutMessageBoxEnabled(bool v)
991 {
992 d->m_timeOutMessageBoxEnabled = v;
993 }
994
setExitCodeInterpreter(const ExitCodeInterpreter & interpreter)995 void QtcProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter)
996 {
997 d->m_exitCodeInterpreter = interpreter;
998 }
999
setWriteData(const QByteArray & writeData)1000 void QtcProcess::setWriteData(const QByteArray &writeData)
1001 {
1002 d->m_writeData = writeData;
1003 setKeepWriteChannelOpen();
1004 }
1005
1006 #ifdef QT_GUI_LIB
isGuiThread()1007 static bool isGuiThread()
1008 {
1009 return QThread::currentThread() == QCoreApplication::instance()->thread();
1010 }
1011 #endif
1012
runBlocking()1013 void QtcProcess::runBlocking()
1014 {
1015 // FIXME: Implement properly
1016
1017 if (d->m_commandLine.executable().needsDevice()) {
1018 QtcProcess::start();
1019 waitForFinished();
1020 return;
1021 };
1022
1023 qCDebug(processLog).noquote() << "Starting blocking:" << d->m_commandLine.toUserOutput()
1024 << " process user events: " << d->m_processUserEvents;
1025 ExecuteOnDestruction logResult([this] { qCDebug(processLog) << *this; });
1026
1027 if (d->m_processUserEvents) {
1028 setOpenMode(d->m_writeData.isEmpty() ? QIODevice::ReadOnly : QIODevice::ReadWrite);
1029 QtcProcess::start();
1030
1031 // On Windows, start failure is triggered immediately if the
1032 // executable cannot be found in the path. Do not start the
1033 // event loop in that case.
1034 if (!d->m_startFailure) {
1035 d->m_timer.start();
1036 #ifdef QT_GUI_LIB
1037 if (isGuiThread())
1038 QApplication::setOverrideCursor(Qt::WaitCursor);
1039 #endif
1040 d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
1041 d->m_stdOut.append(d->m_process->readAllStandardOutput());
1042 d->m_stdErr.append(d->m_process->readAllStandardError());
1043
1044 d->m_timer.stop();
1045 #ifdef QT_GUI_LIB
1046 if (isGuiThread())
1047 QApplication::restoreOverrideCursor();
1048 #endif
1049 }
1050 } else {
1051 setOpenMode(QIODevice::ReadOnly);
1052 QtcProcess::start();
1053 if (!waitForStarted(d->m_maxHangTimerCount * 1000)) {
1054 d->m_result = QtcProcess::StartFailed;
1055 return;
1056 }
1057 closeWriteChannel();
1058 if (!waitForFinished(d->m_maxHangTimerCount * 1000)) {
1059 d->m_result = QtcProcess::Hang;
1060 terminate();
1061 if (!waitForFinished(1000)) {
1062 kill();
1063 waitForFinished(1000);
1064 }
1065 }
1066
1067 if (state() != QProcess::NotRunning)
1068 return;
1069
1070 d->m_stdOut.append(d->m_process->readAllStandardOutput());
1071 d->m_stdErr.append(d->m_process->readAllStandardError());
1072 }
1073 }
1074
setStdOutCallback(const std::function<void (const QString &)> & callback)1075 void QtcProcess::setStdOutCallback(const std::function<void (const QString &)> &callback)
1076 {
1077 d->m_stdOut.outputCallback = callback;
1078 d->m_stdOut.emitSingleLines = false;
1079 }
1080
setStdOutLineCallback(const std::function<void (const QString &)> & callback)1081 void QtcProcess::setStdOutLineCallback(const std::function<void (const QString &)> &callback)
1082 {
1083 d->m_stdOut.outputCallback = callback;
1084 d->m_stdOut.emitSingleLines = true;
1085 d->m_stdOut.keepRawData = false;
1086 }
1087
setStdErrCallback(const std::function<void (const QString &)> & callback)1088 void QtcProcess::setStdErrCallback(const std::function<void (const QString &)> &callback)
1089 {
1090 d->m_stdErr.outputCallback = callback;
1091 d->m_stdErr.emitSingleLines = false;
1092 }
1093
setStdErrLineCallback(const std::function<void (const QString &)> & callback)1094 void QtcProcess::setStdErrLineCallback(const std::function<void (const QString &)> &callback)
1095 {
1096 d->m_stdErr.outputCallback = callback;
1097 d->m_stdErr.emitSingleLines = true;
1098 d->m_stdErr.keepRawData = false;
1099 }
1100
slotTimeout()1101 void QtcProcessPrivate::slotTimeout()
1102 {
1103 if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) {
1104 if (debug)
1105 qDebug() << Q_FUNC_INFO << "HANG detected, killing";
1106 m_waitingForUser = true;
1107 const bool terminate = !m_timeOutMessageBoxEnabled
1108 || askToKill(m_commandLine.executable().toString());
1109 m_waitingForUser = false;
1110 if (terminate) {
1111 q->stopProcess();
1112 m_result = QtcProcess::Hang;
1113 } else {
1114 m_hangTimerCount = 0;
1115 }
1116 } else {
1117 if (debug)
1118 qDebug() << Q_FUNC_INFO << m_hangTimerCount;
1119 }
1120 }
1121
slotFinished(int exitCode,QProcess::ExitStatus status)1122 void QtcProcessPrivate::slotFinished(int exitCode, QProcess::ExitStatus status)
1123 {
1124 if (debug)
1125 qDebug() << Q_FUNC_INFO << exitCode << status;
1126 m_hangTimerCount = 0;
1127 m_exitStatus = status;
1128
1129 switch (status) {
1130 case QProcess::NormalExit:
1131 m_result = interpretExitCode(exitCode);
1132 m_exitCode = exitCode;
1133 break;
1134 case QProcess::CrashExit:
1135 // Was hang detected before and killed?
1136 if (m_result != QtcProcess::Hang)
1137 m_result = QtcProcess::TerminatedAbnormally;
1138 m_exitCode = -1;
1139 break;
1140 }
1141 m_eventLoop.quit();
1142
1143 m_stdOut.handleRest();
1144 m_stdErr.handleRest();
1145
1146 emit q->finished();
1147 }
1148
slotError(QProcess::ProcessError error)1149 void QtcProcessPrivate::slotError(QProcess::ProcessError error)
1150 {
1151 m_hangTimerCount = 0;
1152 if (debug)
1153 qDebug() << Q_FUNC_INFO << error;
1154 // Was hang detected before and killed?
1155 if (m_result != QtcProcess::Hang)
1156 m_result = QtcProcess::StartFailed;
1157 m_startFailure = true;
1158 m_eventLoop.quit();
1159
1160 emit q->errorOccurred(error);
1161 }
1162
1163 } // namespace Utils
1164