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