2 ******************************************************************************
4 * @file synchronousprocess.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
8 * @see The GNU Public License (GPL) Version 3
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "synchronousprocess.h"
31 #include <QtCore/QDebug>
32 #include <QtCore/QTimer>
33 #include <QtCore/QEventLoop>
34 #include <QtCore/QTextCodec>
35 #include <QtCore/QFileInfo>
36 #include <QtCore/QDir>
38 #include <QtWidgets/QApplication>
44 enum { defaultMaxHangTimerCount
= 10 };
47 // ----------- SynchronousProcessResponse
48 SynchronousProcessResponse::SynchronousProcessResponse() :
53 void SynchronousProcessResponse::clear()
61 QTCREATOR_UTILS_EXPORT QDebug
operator<<(QDebug str
, const SynchronousProcessResponse
& r
)
63 QDebug nsp
= str
.nospace();
65 nsp
<< "SynchronousProcessResponse: result=" << r
.result
<< " ex=" << r
.exitCode
<< '\n'
66 << r
.stdOut
.size() << " bytes stdout, stderr=" << r
.stdErr
<< '\n';
70 // Data for one channel buffer (stderr/stdout)
71 struct ChannelBuffer
{
74 QByteArray
linesRead();
78 bool bufferedSignalsEnabled
;
83 ChannelBuffer::ChannelBuffer() :
85 bufferedSignalsEnabled(false),
90 void ChannelBuffer::clearForRun()
97 /* Check for complete lines read from the device and return them, moving the
98 * buffer position. This is based on the assumption that '\n' is the new line
99 * marker in any sane codec. */
100 QByteArray
ChannelBuffer::linesRead()
103 const int lastLineIndex
= data
.lastIndexOf('\n');
105 if (lastLineIndex
== -1 || lastLineIndex
<= bufferPos
) {
108 const int nextBufferPos
= lastLineIndex
+ 1;
109 const QByteArray lines
= data
.mid(bufferPos
, nextBufferPos
- bufferPos
);
110 bufferPos
= nextBufferPos
;
114 // ----------- SynchronousProcessPrivate
115 struct SynchronousProcessPrivate
{
116 SynchronousProcessPrivate();
119 QTextCodec
*m_stdOutCodec
;
122 QEventLoop m_eventLoop
;
123 SynchronousProcessResponse m_result
;
124 int m_hangTimerCount
;
125 int m_maxHangTimerCount
;
128 ChannelBuffer m_stdOut
;
129 ChannelBuffer m_stdErr
;
132 SynchronousProcessPrivate::SynchronousProcessPrivate() :
135 m_maxHangTimerCount(defaultMaxHangTimerCount
),
136 m_startFailure(false)
139 void SynchronousProcessPrivate::clearForRun()
141 m_hangTimerCount
= 0;
142 m_stdOut
.clearForRun();
143 m_stdErr
.clearForRun();
145 m_startFailure
= false;
148 // ----------- SynchronousProcess
149 SynchronousProcess::SynchronousProcess() :
150 m_d(new SynchronousProcessPrivate
)
152 m_d
->m_timer
.setInterval(1000);
153 connect(&m_d
->m_timer
, SIGNAL(timeout()), this, SLOT(slotTimeout()));
154 connect(&m_d
->m_process
, SIGNAL(finished(int, QProcess::ExitStatus
)), this, SLOT(finished(int, QProcess::ExitStatus
)));
155 connect(&m_d
->m_process
, SIGNAL(error(QProcess::ProcessError
)), this, SLOT(error(QProcess::ProcessError
)));
156 connect(&m_d
->m_process
, SIGNAL(readyReadStandardOutput()),
157 this, SLOT(stdOutReady()));
158 connect(&m_d
->m_process
, SIGNAL(readyReadStandardError()),
159 this, SLOT(stdErrReady()));
162 SynchronousProcess::~SynchronousProcess()
167 void SynchronousProcess::setTimeout(int timeoutMS
)
169 if (timeoutMS
>= 0) {
170 m_d
->m_maxHangTimerCount
= qMax(2, timeoutMS
/ 1000);
172 m_d
->m_maxHangTimerCount
= INT_MAX
;
176 int SynchronousProcess::timeout() const
178 return m_d
->m_maxHangTimerCount
== INT_MAX
? -1 : 1000 * m_d
->m_maxHangTimerCount
;
181 void SynchronousProcess::setStdOutCodec(QTextCodec
*c
)
183 m_d
->m_stdOutCodec
= c
;
186 QTextCodec
*SynchronousProcess::stdOutCodec() const
188 return m_d
->m_stdOutCodec
;
191 bool SynchronousProcess::stdOutBufferedSignalsEnabled() const
193 return m_d
->m_stdOut
.bufferedSignalsEnabled
;
196 void SynchronousProcess::setStdOutBufferedSignalsEnabled(bool v
)
198 m_d
->m_stdOut
.bufferedSignalsEnabled
= v
;
201 bool SynchronousProcess::stdErrBufferedSignalsEnabled() const
203 return m_d
->m_stdErr
.bufferedSignalsEnabled
;
206 void SynchronousProcess::setStdErrBufferedSignalsEnabled(bool v
)
208 m_d
->m_stdErr
.bufferedSignalsEnabled
= v
;
211 QStringList
SynchronousProcess::environment() const
213 return m_d
->m_process
.environment();
216 void SynchronousProcess::setEnvironment(const QStringList
&e
)
218 m_d
->m_process
.setEnvironment(e
);
221 void SynchronousProcess::setWorkingDirectory(const QString
&workingDirectory
)
223 m_d
->m_process
.setWorkingDirectory(workingDirectory
);
226 QString
SynchronousProcess::workingDirectory() const
228 return m_d
->m_process
.workingDirectory();
231 QProcess::ProcessChannelMode
SynchronousProcess::processChannelMode() const
233 return m_d
->m_process
.processChannelMode();
236 void SynchronousProcess::setProcessChannelMode(QProcess::ProcessChannelMode m
)
238 m_d
->m_process
.setProcessChannelMode(m
);
241 SynchronousProcessResponse
SynchronousProcess::run(const QString
&binary
,
242 const QStringList
&args
)
245 qDebug() << '>' << Q_FUNC_INFO
<< binary
<< args
;
250 // On Windows, start failure is triggered immediately if the
251 // executable cannot be found in the path. Do not start the
252 // event loop in that case.
253 m_d
->m_process
.start(binary
, args
, QIODevice::ReadOnly
);
254 if (!m_d
->m_startFailure
) {
255 m_d
->m_timer
.start();
256 QApplication::setOverrideCursor(Qt::WaitCursor
);
257 m_d
->m_eventLoop
.exec(QEventLoop::ExcludeUserInputEvents
);
258 if (m_d
->m_result
.result
== SynchronousProcessResponse::Finished
|| m_d
->m_result
.result
== SynchronousProcessResponse::FinishedError
) {
259 processStdOut(false);
260 processStdErr(false);
263 m_d
->m_result
.stdOut
= convertStdOut(m_d
->m_stdOut
.data
);
264 m_d
->m_result
.stdErr
= convertStdErr(m_d
->m_stdErr
.data
);
267 QApplication::restoreOverrideCursor();
271 qDebug() << '<' << Q_FUNC_INFO
<< binary
<< m_d
->m_result
;
273 return m_d
->m_result
;
276 void SynchronousProcess::slotTimeout()
278 if (++m_d
->m_hangTimerCount
> m_d
->m_maxHangTimerCount
) {
280 qDebug() << Q_FUNC_INFO
<< "HANG detected, killing";
282 m_d
->m_process
.kill();
283 m_d
->m_result
.result
= SynchronousProcessResponse::Hang
;
286 qDebug() << Q_FUNC_INFO
<< m_d
->m_hangTimerCount
;
291 void SynchronousProcess::finished(int exitCode
, QProcess::ExitStatus e
)
294 qDebug() << Q_FUNC_INFO
<< exitCode
<< e
;
296 m_d
->m_hangTimerCount
= 0;
298 case QProcess::NormalExit
:
299 m_d
->m_result
.result
= exitCode
? SynchronousProcessResponse::FinishedError
: SynchronousProcessResponse::Finished
;
300 m_d
->m_result
.exitCode
= exitCode
;
302 case QProcess::CrashExit
:
303 // Was hang detected before and killed?
304 if (m_d
->m_result
.result
!= SynchronousProcessResponse::Hang
) {
305 m_d
->m_result
.result
= SynchronousProcessResponse::TerminatedAbnormally
;
307 m_d
->m_result
.exitCode
= -1;
310 m_d
->m_eventLoop
.quit();
313 void SynchronousProcess::error(QProcess::ProcessError e
)
315 m_d
->m_hangTimerCount
= 0;
317 qDebug() << Q_FUNC_INFO
<< e
;
319 // Was hang detected before and killed?
320 if (m_d
->m_result
.result
!= SynchronousProcessResponse::Hang
) {
321 m_d
->m_result
.result
= SynchronousProcessResponse::StartFailed
;
323 m_d
->m_startFailure
= true;
324 m_d
->m_eventLoop
.quit();
327 void SynchronousProcess::stdOutReady()
329 m_d
->m_hangTimerCount
= 0;
333 void SynchronousProcess::stdErrReady()
335 m_d
->m_hangTimerCount
= 0;
339 QString
SynchronousProcess::convertStdErr(const QByteArray
&ba
)
341 return QString::fromLocal8Bit(ba
.constData(), ba
.size()).remove(QLatin1Char('\r'));
344 QString
SynchronousProcess::convertStdOut(const QByteArray
&ba
) const
346 QString stdOut
= m_d
->m_stdOutCodec
? m_d
->m_stdOutCodec
->toUnicode(ba
) : QString::fromLocal8Bit(ba
.constData(), ba
.size());
348 return stdOut
.remove(QLatin1Char('\r'));
351 void SynchronousProcess::processStdOut(bool emitSignals
)
353 // Handle binary data
354 const QByteArray ba
= m_d
->m_process
.readAllStandardOutput();
357 qDebug() << Q_FUNC_INFO
<< emitSignals
<< ba
;
360 m_d
->m_stdOut
.data
+= ba
;
362 // Emit binary signals
363 emit
stdOut(ba
, m_d
->m_stdOut
.firstData
);
364 m_d
->m_stdOut
.firstData
= false;
365 // Buffered. Emit complete lines?
366 if (m_d
->m_stdOut
.bufferedSignalsEnabled
) {
367 const QByteArray lines
= m_d
->m_stdOut
.linesRead();
368 if (!lines
.isEmpty()) {
369 emit
stdOutBuffered(convertStdOut(lines
), m_d
->m_stdOut
.firstBuffer
);
370 m_d
->m_stdOut
.firstBuffer
= false;
377 void SynchronousProcess::processStdErr(bool emitSignals
)
379 // Handle binary data
380 const QByteArray ba
= m_d
->m_process
.readAllStandardError();
383 qDebug() << Q_FUNC_INFO
<< emitSignals
<< ba
;
386 m_d
->m_stdErr
.data
+= ba
;
388 // Emit binary signals
389 emit
stdErr(ba
, m_d
->m_stdErr
.firstData
);
390 m_d
->m_stdErr
.firstData
= false;
391 if (m_d
->m_stdErr
.bufferedSignalsEnabled
) {
392 // Buffered. Emit complete lines?
393 const QByteArray lines
= m_d
->m_stdErr
.linesRead();
394 if (!lines
.isEmpty()) {
395 emit
stdErrBuffered(convertStdErr(lines
), m_d
->m_stdErr
.firstBuffer
);
396 m_d
->m_stdErr
.firstBuffer
= false;
405 enum OS_Type
{ OS_Mac
, OS_Windows
, OS_Unix
};
408 static const OS_Type pathOS
= OS_Windows
;
411 static const OS_Type pathOS
= OS_Mac
;
413 static const OS_Type pathOS
= OS_Unix
;
417 // Locate a binary in a directory, applying all kinds of
418 // extensions the operating system supports.
419 static QString
checkBinary(const QDir
&dir
, const QString
&binary
)
421 // naive UNIX approach
422 const QFileInfo
info(dir
.filePath(binary
));
424 if (info
.isFile() && info
.isExecutable()) {
425 return info
.absoluteFilePath();
428 // Does the OS have some weird extension concept or does the
429 // binary have a 3 letter extension?
430 if (pathOS
== OS_Unix
) {
433 const int dotIndex
= binary
.lastIndexOf(QLatin1Char('.'));
434 if (dotIndex
!= -1 && dotIndex
== binary
.size() - 4) {
443 static const char *windowsExtensions
[] = { ".cmd", ".bat", ".exe", ".com" };
444 // Check the Windows extensions using the order
445 const int windowsExtensionCount
= sizeof(windowsExtensions
) / sizeof(const char *);
446 for (int e
= 0; e
< windowsExtensionCount
; e
++) {
447 const QFileInfo
windowsBinary(dir
.filePath(binary
+ QLatin1String(windowsExtensions
[e
])));
448 if (windowsBinary
.isFile() && windowsBinary
.isExecutable()) {
449 return windowsBinary
.absoluteFilePath();
456 // Check for Mac app folders
457 const QFileInfo
appFolder(dir
.filePath(binary
+ QLatin1String(".app")));
458 if (appFolder
.isDir()) {
459 QString macBinaryPath
= appFolder
.absoluteFilePath();
460 macBinaryPath
+= QLatin1String("/Contents/MacOS/");
461 macBinaryPath
+= binary
;
462 const QFileInfo
macBinary(macBinaryPath
);
463 if (macBinary
.isFile() && macBinary
.isExecutable()) {
464 return macBinary
.absoluteFilePath();
473 QString
SynchronousProcess::locateBinary(const QString
&path
, const QString
&binary
)
476 const QFileInfo
absInfo(binary
);
478 if (absInfo
.isAbsolute()) {
479 return checkBinary(absInfo
.dir(), absInfo
.fileName());
482 // Windows finds binaries in the current directory
483 if (pathOS
== OS_Windows
) {
484 const QString currentDirBinary
= checkBinary(QDir::current(), binary
);
485 if (!currentDirBinary
.isEmpty()) {
486 return currentDirBinary
;
490 const QStringList paths
= path
.split(pathSeparator());
494 const QStringList::const_iterator cend
= paths
.constEnd();
495 for (QStringList::const_iterator it
= paths
.constBegin(); it
!= cend
; ++it
) {
497 const QString rc
= checkBinary(dir
, binary
);
505 QString
SynchronousProcess::locateBinary(const QString
&binary
)
507 const QByteArray path
= qgetenv("PATH");
509 return locateBinary(QString::fromLocal8Bit(path
), binary
);
512 QChar
SynchronousProcess::pathSeparator()
514 if (pathOS
== OS_Windows
) {
515 return QLatin1Char(';');
517 return QLatin1Char(':');