2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-15 14:58:39 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2010-12-17 17:14:20 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2010-10-14 18:05:43 +02:00
|
|
|
|
|
|
|
|
#include "qtcprocess.h"
|
|
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
#include "stringutils.h"
|
|
|
|
|
#include "executeondestruction.h"
|
|
|
|
|
#include "hostosinfo.h"
|
|
|
|
|
#include "qtcassert.h"
|
|
|
|
|
#include "qtcprocess.h"
|
2013-05-03 13:52:52 +02:00
|
|
|
|
2021-04-30 12:31:36 +02:00
|
|
|
#include <QCoreApplication>
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QDebug>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QDir>
|
|
|
|
|
#include <QLoggingCategory>
|
2020-06-11 15:23:04 +02:00
|
|
|
#include <QRegularExpression>
|
2014-02-05 10:43:21 +01:00
|
|
|
#include <QStack>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QTextCodec>
|
2021-04-30 12:31:36 +02:00
|
|
|
#include <QThread>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QTimer>
|
2021-04-30 12:31:36 +02:00
|
|
|
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
// qmlpuppet does not use that.
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#endif
|
2011-02-28 10:14:52 +01:00
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2021-05-01 21:04:48 +02:00
|
|
|
#define CALLBACK WINAPI
|
2011-08-03 12:04:46 +02:00
|
|
|
#include <qt_windows.h>
|
2019-09-10 23:39:29 +03:00
|
|
|
#else
|
|
|
|
|
#include <errno.h>
|
2020-02-18 15:33:15 -08:00
|
|
|
#include <stdio.h>
|
2019-09-10 23:39:29 +03:00
|
|
|
#include <unistd.h>
|
2011-08-03 12:04:46 +02:00
|
|
|
#endif
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
|
|
|
|
|
// The main state of the Unix shell parser
|
|
|
|
|
enum MxQuoting { MxBasic, MxSingleQuote, MxDoubleQuote, MxParen, MxSubst, MxGroup, MxMath };
|
|
|
|
|
|
|
|
|
|
struct MxState
|
|
|
|
|
{
|
|
|
|
|
MxQuoting current;
|
|
|
|
|
// Bizarrely enough, double quoting has an impact on the behavior of some
|
|
|
|
|
// complex expressions within the quoted string.
|
|
|
|
|
bool dquote;
|
|
|
|
|
};
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
Q_DECLARE_TYPEINFO(MxState, Q_PRIMITIVE_TYPE);
|
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
|
|
|
|
|
|
// Pushed state for the case where a $(()) expansion turns out bogus
|
|
|
|
|
struct MxSave
|
|
|
|
|
{
|
|
|
|
|
QString str;
|
|
|
|
|
int pos, varPos;
|
|
|
|
|
};
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
Q_DECLARE_TYPEINFO(MxSave, Q_MOVABLE_TYPE);
|
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
using namespace Utils::Internal;
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
namespace Utils {
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
enum { debug = 0 };
|
|
|
|
|
enum { syncDebug = 0 };
|
|
|
|
|
|
|
|
|
|
enum { defaultMaxHangTimerCount = 10 };
|
|
|
|
|
|
|
|
|
|
static Q_LOGGING_CATEGORY(processLog, "qtc.utils.synchronousprocess", QtWarningMsg);
|
|
|
|
|
|
2021-04-27 15:18:58 +02:00
|
|
|
static std::function<void(QtcProcess &)> s_remoteRunProcessHook;
|
|
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
/*!
|
|
|
|
|
\class Utils::QtcProcess
|
|
|
|
|
|
2013-06-05 14:29:24 +02:00
|
|
|
\brief The QtcProcess class provides functionality for dealing with
|
|
|
|
|
shell-quoted process arguments.
|
2011-03-02 17:13:33 +01:00
|
|
|
*/
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
inline static bool isMetaCharWin(ushort c)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
|
|
|
|
|
}; // &()<>|
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static void envExpandWin(QString &args, const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const QString cdName = QLatin1String("CD");
|
|
|
|
|
int off = 0;
|
|
|
|
|
next:
|
|
|
|
|
for (int prev = -1, that;
|
|
|
|
|
(that = args.indexOf(QLatin1Char('%'), off)) >= 0;
|
|
|
|
|
prev = that, off = that + 1) {
|
|
|
|
|
if (prev >= 0) {
|
|
|
|
|
const QString var = args.mid(prev + 1, that - prev - 1).toUpper();
|
|
|
|
|
const QString val = (var == cdName && pwd && !pwd->isEmpty())
|
2019-08-19 14:29:14 +02:00
|
|
|
? QDir::toNativeSeparators(*pwd) : env->expandedValueForKey(var);
|
2010-10-14 18:05:43 +02:00
|
|
|
if (!val.isEmpty()) { // Empty values are impossible, so this is an existence check
|
|
|
|
|
args.replace(prev, that - prev + 1, val);
|
|
|
|
|
off = prev + val.length();
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QtcProcess::Arguments prepareArgsWin(const QString &_args, QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString args(_args);
|
|
|
|
|
|
|
|
|
|
if (env) {
|
2014-02-05 10:43:21 +01:00
|
|
|
envExpandWin(args, env, pwd);
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
if (args.indexOf(QLatin1Char('%')) >= 0) {
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::FoundMeta;
|
|
|
|
|
return QtcProcess::Arguments::createWindowsArgs(QString());
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!args.isEmpty() && args.unicode()[0].unicode() == '@')
|
|
|
|
|
args.remove(0, 1);
|
|
|
|
|
|
|
|
|
|
for (int p = 0; p < args.length(); p++) {
|
|
|
|
|
ushort c = args.unicode()[p].unicode();
|
|
|
|
|
if (c == '^') {
|
|
|
|
|
args.remove(p, 1);
|
|
|
|
|
} else if (c == '"') {
|
|
|
|
|
do {
|
|
|
|
|
if (++p == args.length())
|
|
|
|
|
break; // For cmd, this is no error.
|
|
|
|
|
} while (args.unicode()[p].unicode() != '"');
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (isMetaCharWin(c)) {
|
2010-10-14 18:05:43 +02:00
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::FoundMeta;
|
|
|
|
|
return QtcProcess::Arguments::createWindowsArgs(QString());
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::SplitOk;
|
|
|
|
|
return QtcProcess::Arguments::createWindowsArgs(args);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
inline static bool isWhiteSpaceWin(ushort c)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
return c == ' ' || c == '\t';
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QStringList doSplitArgsWin(const QString &args, QtcProcess::SplitError *err)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QStringList ret;
|
|
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
*err = QtcProcess::SplitOk;
|
|
|
|
|
|
|
|
|
|
int p = 0;
|
|
|
|
|
const int length = args.length();
|
|
|
|
|
forever {
|
|
|
|
|
forever {
|
|
|
|
|
if (p == length)
|
|
|
|
|
return ret;
|
2014-02-05 10:43:21 +01:00
|
|
|
if (!isWhiteSpaceWin(args.unicode()[p].unicode()))
|
2010-10-14 18:05:43 +02:00
|
|
|
break;
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString arg;
|
|
|
|
|
bool inquote = false;
|
|
|
|
|
forever {
|
|
|
|
|
bool copy = true; // copy this char
|
|
|
|
|
int bslashes = 0; // number of preceding backslashes to insert
|
|
|
|
|
while (p < length && args.unicode()[p] == QLatin1Char('\\')) {
|
|
|
|
|
++p;
|
|
|
|
|
++bslashes;
|
|
|
|
|
}
|
|
|
|
|
if (p < length && args.unicode()[p] == QLatin1Char('"')) {
|
|
|
|
|
if (!(bslashes & 1)) {
|
|
|
|
|
// Even number of backslashes, so the quote is not escaped.
|
|
|
|
|
if (inquote) {
|
|
|
|
|
if (p + 1 < length && args.unicode()[p + 1] == QLatin1Char('"')) {
|
|
|
|
|
// This is not documented on MSDN.
|
|
|
|
|
// Two consecutive quotes make a literal quote. Brain damage:
|
|
|
|
|
// this still closes the quoting, so a 3rd quote is required,
|
|
|
|
|
// which makes the runtime's quoting run out of sync with the
|
|
|
|
|
// shell's one unless the 2nd quote is escaped.
|
|
|
|
|
++p;
|
|
|
|
|
} else {
|
|
|
|
|
// Closing quote
|
|
|
|
|
copy = false;
|
|
|
|
|
}
|
|
|
|
|
inquote = false;
|
|
|
|
|
} else {
|
|
|
|
|
// Opening quote
|
|
|
|
|
copy = false;
|
|
|
|
|
inquote = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bslashes >>= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (--bslashes >= 0)
|
|
|
|
|
arg.append(QLatin1Char('\\'));
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
if (p == length || (!inquote && isWhiteSpaceWin(args.unicode()[p].unicode()))) {
|
2010-10-14 18:05:43 +02:00
|
|
|
ret.append(arg);
|
|
|
|
|
if (inquote) {
|
|
|
|
|
if (err)
|
|
|
|
|
*err = QtcProcess::BadQuoting;
|
|
|
|
|
return QStringList();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (copy)
|
|
|
|
|
arg.append(args.unicode()[p]);
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//not reached
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
/*!
|
2013-09-06 13:23:11 +02:00
|
|
|
Splits \a _args according to system shell word splitting and quoting rules.
|
2011-03-02 17:13:33 +01:00
|
|
|
|
|
|
|
|
\section1 Unix
|
|
|
|
|
|
|
|
|
|
The behavior is based on the POSIX shell and bash:
|
|
|
|
|
\list
|
2013-09-06 13:23:11 +02:00
|
|
|
\li Whitespace splits tokens.
|
|
|
|
|
\li The backslash quotes the following character.
|
2013-02-06 08:50:23 +01:00
|
|
|
\li A string enclosed in single quotes is not split. No shell meta
|
2011-03-02 17:13:33 +01:00
|
|
|
characters are interpreted.
|
2013-02-06 08:50:23 +01:00
|
|
|
\li A string enclosed in double quotes is not split. Within the string,
|
2011-03-02 17:13:33 +01:00
|
|
|
the backslash quotes shell meta characters - if it is followed
|
|
|
|
|
by a "meaningless" character, the backslash is output verbatim.
|
|
|
|
|
\endlist
|
|
|
|
|
If \a abortOnMeta is \c false, only the splitting and quoting rules apply,
|
|
|
|
|
while other meta characters (substitutions, redirections, etc.) are ignored.
|
|
|
|
|
If \a abortOnMeta is \c true, encounters of unhandled meta characters are
|
|
|
|
|
treated as errors.
|
|
|
|
|
|
2013-09-06 13:23:11 +02:00
|
|
|
If \a err is not NULL, stores a status code at the pointer target. For more
|
|
|
|
|
information, see \l SplitError.
|
|
|
|
|
|
|
|
|
|
If \env is not NULL, performs variable substitution with the
|
|
|
|
|
given environment.
|
|
|
|
|
|
|
|
|
|
Returns a list of unquoted words or an empty list if an error occurred.
|
|
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
\section1 Windows
|
|
|
|
|
|
|
|
|
|
The behavior is defined by the Microsoft C runtime:
|
|
|
|
|
\list
|
2013-09-06 13:23:11 +02:00
|
|
|
\li Whitespace splits tokens.
|
|
|
|
|
\li A string enclosed in double quotes is not split.
|
2011-03-02 17:13:33 +01:00
|
|
|
\list
|
2013-02-06 08:50:23 +01:00
|
|
|
\li 3N double quotes within a quoted string yield N literal quotes.
|
2011-03-02 17:13:33 +01:00
|
|
|
This is not documented on MSDN.
|
|
|
|
|
\endlist
|
2013-09-06 13:23:11 +02:00
|
|
|
\li Backslashes have special semantics if they are followed by a double quote:
|
2011-03-02 17:13:33 +01:00
|
|
|
\list
|
2013-02-06 08:50:23 +01:00
|
|
|
\li 2N backslashes + double quote => N backslashes and begin/end quoting
|
|
|
|
|
\li 2N+1 backslashes + double quote => N backslashes + literal quote
|
2011-03-02 17:13:33 +01:00
|
|
|
\endlist
|
|
|
|
|
\endlist
|
|
|
|
|
Qt and many other implementations comply with this standard, but many do not.
|
|
|
|
|
|
|
|
|
|
If \a abortOnMeta is \c true, cmd shell semantics are applied before
|
|
|
|
|
proceeding with word splitting:
|
|
|
|
|
\list
|
2013-02-06 08:50:23 +01:00
|
|
|
\li Cmd ignores \e all special chars between double quotes.
|
2011-03-28 14:58:12 +02:00
|
|
|
Note that the quotes are \e not removed at this stage - the
|
2011-03-02 17:13:33 +01:00
|
|
|
tokenization rules described above still apply.
|
2013-02-06 08:50:23 +01:00
|
|
|
\li The \c circumflex is the escape char for everything including itself.
|
2011-03-02 17:13:33 +01:00
|
|
|
\endlist
|
|
|
|
|
As the quoting levels are independent from each other and have different
|
|
|
|
|
semantics, you need a command line like \c{"foo "\^"" bar"} to get
|
|
|
|
|
\c{foo " bar}.
|
|
|
|
|
*/
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
|
|
|
|
|
static QStringList splitArgsWin(const QString &_args, bool abortOnMeta,
|
|
|
|
|
QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
if (abortOnMeta) {
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::SplitError perr;
|
2010-10-14 18:05:43 +02:00
|
|
|
if (!err)
|
|
|
|
|
err = &perr;
|
2014-02-05 10:43:21 +01:00
|
|
|
QString args = prepareArgsWin(_args, &perr, env, pwd).toWindowsArgs();
|
|
|
|
|
if (*err != QtcProcess::SplitOk)
|
2010-10-14 18:05:43 +02:00
|
|
|
return QStringList();
|
2014-02-05 10:43:21 +01:00
|
|
|
return doSplitArgsWin(args, err);
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
QString args = _args;
|
|
|
|
|
if (env)
|
2014-02-05 10:43:21 +01:00
|
|
|
envExpandWin(args, env, pwd);
|
|
|
|
|
return doSplitArgsWin(args, err);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static bool isMetaUnix(QChar cUnicode)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8,
|
|
|
|
|
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x38
|
|
|
|
|
}; // \'"$`<>|;&(){}*?#[]
|
|
|
|
|
|
|
|
|
|
uint c = cUnicode.unicode();
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QStringList splitArgsUnix(const QString &args, bool abortOnMeta,
|
|
|
|
|
QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
static const QString pwdName = QLatin1String("PWD");
|
|
|
|
|
QStringList ret;
|
|
|
|
|
|
|
|
|
|
for (int pos = 0; ; ) {
|
|
|
|
|
QChar c;
|
|
|
|
|
do {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto okret;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
} while (c.isSpace());
|
|
|
|
|
QString cret;
|
|
|
|
|
bool hadWord = false;
|
2011-03-28 14:50:32 +02:00
|
|
|
if (c == QLatin1Char('~')) {
|
|
|
|
|
if (pos >= args.length()
|
|
|
|
|
|| args.unicode()[pos].isSpace() || args.unicode()[pos] == QLatin1Char('/')) {
|
|
|
|
|
cret = QDir::homePath();
|
|
|
|
|
hadWord = true;
|
|
|
|
|
goto getc;
|
|
|
|
|
} else if (abortOnMeta) {
|
|
|
|
|
goto metaerr;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
do {
|
|
|
|
|
if (c == QLatin1Char('\'')) {
|
|
|
|
|
int spos = pos;
|
|
|
|
|
do {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
} while (c != QLatin1Char('\''));
|
2020-09-07 10:08:59 +02:00
|
|
|
cret += args.mid(spos, pos - spos - 1);
|
2010-10-14 18:05:43 +02:00
|
|
|
hadWord = true;
|
|
|
|
|
} else if (c == QLatin1Char('"')) {
|
|
|
|
|
for (;;) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
nextq:
|
|
|
|
|
if (c == QLatin1Char('"'))
|
|
|
|
|
break;
|
|
|
|
|
if (c == QLatin1Char('\\')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
if (c != QLatin1Char('"') &&
|
|
|
|
|
c != QLatin1Char('\\') &&
|
|
|
|
|
!(abortOnMeta &&
|
|
|
|
|
(c == QLatin1Char('$') ||
|
|
|
|
|
c == QLatin1Char('`'))))
|
|
|
|
|
cret += QLatin1Char('\\');
|
|
|
|
|
} else if (c == QLatin1Char('$') && env) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
bool braced = false;
|
|
|
|
|
if (c == QLatin1Char('{')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
braced = true;
|
|
|
|
|
}
|
|
|
|
|
QString var;
|
|
|
|
|
while (c.isLetterOrNumber() || c == QLatin1Char('_')) {
|
|
|
|
|
var += c;
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
}
|
|
|
|
|
if (var == pwdName && pwd && !pwd->isEmpty()) {
|
|
|
|
|
cret += *pwd;
|
|
|
|
|
} else {
|
|
|
|
|
Environment::const_iterator vit = env->constFind(var);
|
|
|
|
|
if (vit == env->constEnd()) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a shell builtin
|
|
|
|
|
} else {
|
2019-08-19 14:29:14 +02:00
|
|
|
cret += env->expandedValueForKey(env->key(vit));
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!braced)
|
|
|
|
|
goto nextq;
|
|
|
|
|
if (c != QLatin1Char('}')) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a complex expansion
|
|
|
|
|
goto quoteerr; // Otherwise it's just garbage
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else if (abortOnMeta &&
|
|
|
|
|
(c == QLatin1Char('$') ||
|
|
|
|
|
c == QLatin1Char('`'))) {
|
|
|
|
|
goto metaerr;
|
|
|
|
|
}
|
|
|
|
|
cret += c;
|
|
|
|
|
}
|
|
|
|
|
hadWord = true;
|
|
|
|
|
} else if (c == QLatin1Char('$') && env) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr; // Bash just takes it verbatim, but whatever
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
bool braced = false;
|
|
|
|
|
if (c == QLatin1Char('{')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
braced = true;
|
|
|
|
|
}
|
|
|
|
|
QString var;
|
|
|
|
|
while (c.isLetterOrNumber() || c == QLatin1Char('_')) {
|
|
|
|
|
var += c;
|
|
|
|
|
if (pos >= args.length()) {
|
|
|
|
|
if (braced)
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = QLatin1Char(' ');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
}
|
|
|
|
|
QString val;
|
|
|
|
|
if (var == pwdName && pwd && !pwd->isEmpty()) {
|
|
|
|
|
val = *pwd;
|
|
|
|
|
} else {
|
|
|
|
|
Environment::const_iterator vit = env->constFind(var);
|
|
|
|
|
if (vit == env->constEnd()) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a shell builtin
|
|
|
|
|
} else {
|
2019-08-19 14:29:14 +02:00
|
|
|
val = env->expandedValueForKey(env->key(vit));
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < val.length(); i++) {
|
2016-05-18 12:37:39 +02:00
|
|
|
const QChar cc = val.unicode()[i];
|
|
|
|
|
if (cc.unicode() == 9 || cc.unicode() == 10 || cc.unicode() == 32) {
|
2010-10-14 18:05:43 +02:00
|
|
|
if (hadWord) {
|
|
|
|
|
ret += cret;
|
|
|
|
|
cret.clear();
|
|
|
|
|
hadWord = false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
cret += cc;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!braced)
|
|
|
|
|
goto nextc;
|
|
|
|
|
if (c != QLatin1Char('}')) {
|
|
|
|
|
if (abortOnMeta)
|
|
|
|
|
goto metaerr; // Assume this is a complex expansion
|
|
|
|
|
goto quoteerr; // Otherwise it's just garbage
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (c == QLatin1Char('\\')) {
|
|
|
|
|
if (pos >= args.length())
|
|
|
|
|
goto quoteerr;
|
|
|
|
|
c = args.unicode()[pos++];
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (abortOnMeta && isMetaUnix(c)) {
|
2010-10-14 18:05:43 +02:00
|
|
|
goto metaerr;
|
|
|
|
|
}
|
|
|
|
|
cret += c;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
}
|
2011-03-28 14:50:32 +02:00
|
|
|
getc:
|
2010-10-14 18:05:43 +02:00
|
|
|
if (pos >= args.length())
|
|
|
|
|
break;
|
|
|
|
|
c = args.unicode()[pos++];
|
|
|
|
|
nextc: ;
|
|
|
|
|
} while (!c.isSpace());
|
|
|
|
|
if (hadWord)
|
|
|
|
|
ret += cret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
okret:
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::SplitOk;
|
2010-10-14 18:05:43 +02:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
quoteerr:
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::BadQuoting;
|
2010-10-14 18:05:43 +02:00
|
|
|
return QStringList();
|
|
|
|
|
|
|
|
|
|
metaerr:
|
|
|
|
|
if (err)
|
2014-02-05 10:43:21 +01:00
|
|
|
*err = QtcProcess::FoundMeta;
|
2010-10-14 18:05:43 +02:00
|
|
|
return QStringList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline static bool isSpecialCharUnix(ushort c)
|
|
|
|
|
{
|
|
|
|
|
// Chars that should be quoted (TM). This includes:
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
|
|
|
|
|
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
|
|
|
|
|
}; // 0-32 \'"$`<>|;&(){}*?#!~[]
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline static bool hasSpecialCharsUnix(const QString &arg)
|
|
|
|
|
{
|
|
|
|
|
for (int x = arg.length() - 1; x >= 0; --x)
|
|
|
|
|
if (isSpecialCharUnix(arg.unicode()[x].unicode()))
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QStringList QtcProcess::splitArgs(const QString &args, OsType osType,
|
|
|
|
|
bool abortOnMeta, QtcProcess::SplitError *err,
|
|
|
|
|
const Environment *env, const QString *pwd)
|
|
|
|
|
{
|
|
|
|
|
if (osType == OsTypeWindows)
|
|
|
|
|
return splitArgsWin(args, abortOnMeta, err, env, pwd);
|
|
|
|
|
else
|
|
|
|
|
return splitArgsUnix(args, abortOnMeta, err, env, pwd);
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
QString QtcProcess::quoteArgUnix(const QString &arg)
|
|
|
|
|
{
|
2020-01-15 19:10:34 +01:00
|
|
|
if (arg.isEmpty())
|
2010-10-14 18:05:43 +02:00
|
|
|
return QString::fromLatin1("''");
|
|
|
|
|
|
|
|
|
|
QString ret(arg);
|
|
|
|
|
if (hasSpecialCharsUnix(ret)) {
|
|
|
|
|
ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
|
|
|
|
|
ret.prepend(QLatin1Char('\''));
|
|
|
|
|
ret.append(QLatin1Char('\''));
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static bool isSpecialCharWin(ushort c)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
// Chars that should be quoted (TM). This includes:
|
|
|
|
|
// - control chars & space
|
|
|
|
|
// - the shell meta chars "&()<>^|
|
|
|
|
|
// - the potential separators ,;=
|
|
|
|
|
static const uchar iqm[] = {
|
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
|
|
|
|
|
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static bool hasSpecialCharsWin(const QString &arg)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
for (int x = arg.length() - 1; x >= 0; --x)
|
2014-02-05 10:43:21 +01:00
|
|
|
if (isSpecialCharWin(arg.unicode()[x].unicode()))
|
2010-10-14 18:05:43 +02:00
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
static QString quoteArgWin(const QString &arg)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
2020-01-15 19:10:34 +01:00
|
|
|
if (arg.isEmpty())
|
2010-10-14 18:05:43 +02:00
|
|
|
return QString::fromLatin1("\"\"");
|
|
|
|
|
|
|
|
|
|
QString ret(arg);
|
2014-02-05 10:43:21 +01:00
|
|
|
if (hasSpecialCharsWin(ret)) {
|
2010-10-14 18:05:43 +02:00
|
|
|
// Quotes are escaped and their preceding backslashes are doubled.
|
|
|
|
|
// It's impossible to escape anything inside a quoted string on cmd
|
|
|
|
|
// level, so the outer quoting must be "suspended".
|
2020-06-11 15:23:04 +02:00
|
|
|
ret.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
|
2010-10-14 18:05:43 +02:00
|
|
|
// The argument must not end with a \ since this would be interpreted
|
|
|
|
|
// as escaping the quote -- rather put the \ behind the quote: e.g.
|
|
|
|
|
// rather use "foo"\ than "foo\"
|
2011-06-30 18:44:44 +02:00
|
|
|
int i = ret.length();
|
|
|
|
|
while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
|
|
|
|
|
--i;
|
|
|
|
|
ret.insert(i, QLatin1Char('"'));
|
2010-10-14 18:05:43 +02:00
|
|
|
ret.prepend(QLatin1Char('"'));
|
|
|
|
|
}
|
|
|
|
|
// FIXME: Without this, quoting is not foolproof. But it needs support in the process setup, etc.
|
|
|
|
|
//ret.replace('%', QLatin1String("%PERCENT_SIGN%"));
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::Arguments QtcProcess::prepareArgs(const QString &cmd, SplitError *err, OsType osType,
|
2019-06-14 13:37:20 +02:00
|
|
|
const Environment *env, const QString *pwd, bool abortOnMeta)
|
2014-02-05 10:43:21 +01:00
|
|
|
{
|
|
|
|
|
if (osType == OsTypeWindows)
|
|
|
|
|
return prepareArgsWin(cmd, err, env, pwd);
|
|
|
|
|
else
|
2019-06-14 13:37:20 +02:00
|
|
|
return Arguments::createUnixArgs(splitArgs(cmd, osType, abortOnMeta, err, env, pwd));
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::quoteArg(const QString &arg, OsType osType)
|
|
|
|
|
{
|
|
|
|
|
if (osType == OsTypeWindows)
|
|
|
|
|
return quoteArgWin(arg);
|
|
|
|
|
else
|
|
|
|
|
return quoteArgUnix(arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::addArg(QString *args, const QString &arg, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
if (!args->isEmpty())
|
|
|
|
|
*args += QLatin1Char(' ');
|
2014-02-05 10:43:21 +01:00
|
|
|
*args += quoteArg(arg, osType);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QString QtcProcess::joinArgs(const QStringList &args, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString ret;
|
2020-06-13 23:39:57 +03:00
|
|
|
for (const QString &arg : args)
|
2014-02-05 10:43:21 +01:00
|
|
|
addArg(&ret, arg, osType);
|
2010-10-14 18:05:43 +02:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::addArgs(QString *args, const QString &inArgs)
|
|
|
|
|
{
|
|
|
|
|
if (!inArgs.isEmpty()) {
|
|
|
|
|
if (!args->isEmpty())
|
|
|
|
|
*args += QLatin1Char(' ');
|
|
|
|
|
*args += inArgs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::addArgs(QString *args, const QStringList &inArgs)
|
|
|
|
|
{
|
2020-06-13 23:39:57 +03:00
|
|
|
for (const QString &arg : inArgs)
|
2010-10-14 18:05:43 +02:00
|
|
|
addArg(args, arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::prepareCommand(const QString &command, const QString &arguments,
|
2014-02-05 10:43:21 +01:00
|
|
|
QString *outCmd, Arguments *outArgs, OsType osType,
|
2010-10-14 18:05:43 +02:00
|
|
|
const Environment *env, const QString *pwd)
|
|
|
|
|
{
|
|
|
|
|
QtcProcess::SplitError err;
|
2014-02-05 10:43:21 +01:00
|
|
|
*outArgs = QtcProcess::prepareArgs(arguments, &err, osType, env, pwd);
|
2010-10-14 18:05:43 +02:00
|
|
|
if (err == QtcProcess::SplitOk) {
|
|
|
|
|
*outCmd = command;
|
|
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (osType == OsTypeWindows) {
|
|
|
|
|
*outCmd = QString::fromLatin1(qgetenv("COMSPEC"));
|
|
|
|
|
*outArgs = Arguments::createWindowsArgs(QLatin1String("/v:off /s /c \"")
|
|
|
|
|
+ quoteArg(QDir::toNativeSeparators(command)) + QLatin1Char(' ') + arguments
|
|
|
|
|
+ QLatin1Char('"'));
|
|
|
|
|
} else {
|
|
|
|
|
if (err != QtcProcess::FoundMeta)
|
|
|
|
|
return false;
|
2020-10-05 14:01:40 +02:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
2020-09-23 12:49:38 +02:00
|
|
|
*outCmd = qEnvironmentVariable("SHELL", "/bin/sh");
|
2020-10-05 14:01:40 +02:00
|
|
|
#else
|
|
|
|
|
// for sdktool
|
|
|
|
|
*outCmd = qEnvironmentVariableIsSet("SHELL") ? QString::fromLocal8Bit(qgetenv("SHELL"))
|
|
|
|
|
: QString("/bin/sh");
|
|
|
|
|
#endif
|
2017-02-09 14:00:07 +01:00
|
|
|
*outArgs = Arguments::createUnixArgs(
|
2017-02-22 15:09:35 +01:00
|
|
|
QStringList({"-c", (quoteArg(command) + ' ' + arguments)}));
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
namespace Internal {
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
// Data for one channel buffer (stderr/stdout)
|
|
|
|
|
class ChannelBuffer : public QObject
|
2021-05-05 16:05:53 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2021-05-06 11:15:28 +02:00
|
|
|
void clearForRun();
|
|
|
|
|
|
|
|
|
|
QString linesRead();
|
|
|
|
|
void append(const QByteArray &text, bool emitSignals);
|
|
|
|
|
|
|
|
|
|
QByteArray rawData;
|
|
|
|
|
QString incompleteLineBuffer; // lines not yet signaled
|
|
|
|
|
QTextCodec *codec = nullptr; // Not owner
|
|
|
|
|
std::unique_ptr<QTextCodec::ConverterState> codecState;
|
|
|
|
|
int rawDataPos = 0;
|
|
|
|
|
std::function<void(const QString &lines)> outputCallback;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class QtcProcessPrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit QtcProcessPrivate(QtcProcess *parent) : q(parent) {}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void setupChildProcess_impl();
|
|
|
|
|
|
|
|
|
|
CommandLine m_commandLine;
|
|
|
|
|
Environment m_environment;
|
|
|
|
|
bool m_haveEnv = false;
|
|
|
|
|
bool m_useCtrlCStub = false;
|
|
|
|
|
bool m_lowPriority = false;
|
|
|
|
|
bool m_disableUnixTerminal = false;
|
|
|
|
|
|
|
|
|
|
bool m_synchronous = false;
|
|
|
|
|
QProcess::OpenMode m_openMode = QProcess::ReadWrite;
|
2021-05-06 11:15:28 +02:00
|
|
|
|
|
|
|
|
// SynchronousProcess left overs:
|
|
|
|
|
void slotTimeout();
|
|
|
|
|
void slotFinished(int exitCode, QProcess::ExitStatus e);
|
|
|
|
|
void slotError(QProcess::ProcessError);
|
|
|
|
|
void processStdOut(bool emitSignals);
|
|
|
|
|
void processStdErr(bool emitSignals);
|
|
|
|
|
void clearForRun();
|
|
|
|
|
|
|
|
|
|
QtcProcess *q;
|
|
|
|
|
QTextCodec *m_codec = QTextCodec::codecForLocale();
|
|
|
|
|
QTimer m_timer;
|
|
|
|
|
QEventLoop m_eventLoop;
|
|
|
|
|
SynchronousProcessResponse m_result;
|
|
|
|
|
FilePath m_binary;
|
|
|
|
|
ChannelBuffer m_stdOut;
|
|
|
|
|
ChannelBuffer m_stdErr;
|
|
|
|
|
ExitCodeInterpreter m_exitCodeInterpreter = defaultExitCodeInterpreter;
|
|
|
|
|
|
|
|
|
|
int m_hangTimerCount = 0;
|
|
|
|
|
int m_maxHangTimerCount = defaultMaxHangTimerCount;
|
|
|
|
|
bool m_startFailure = false;
|
|
|
|
|
bool m_timeOutMessageBoxEnabled = false;
|
|
|
|
|
bool m_waitingForUser = false;
|
2021-05-05 16:05:53 +02:00
|
|
|
};
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::clearForRun()
|
|
|
|
|
{
|
|
|
|
|
m_hangTimerCount = 0;
|
|
|
|
|
m_stdOut.clearForRun();
|
|
|
|
|
m_stdOut.codec = m_codec;
|
|
|
|
|
m_stdErr.clearForRun();
|
|
|
|
|
m_stdErr.codec = m_codec;
|
|
|
|
|
m_result.clear();
|
|
|
|
|
m_result.codec = m_codec;
|
|
|
|
|
m_startFailure = false;
|
|
|
|
|
m_binary = {};
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
} // Internal
|
|
|
|
|
|
2016-06-08 13:30:30 +02:00
|
|
|
QtcProcess::QtcProcess(QObject *parent)
|
2021-05-06 11:15:28 +02:00
|
|
|
: QProcess(parent), d(new QtcProcessPrivate(this))
|
2016-06-08 13:30:30 +02:00
|
|
|
{
|
|
|
|
|
static int qProcessExitStatusMeta = qRegisterMetaType<QProcess::ExitStatus>();
|
|
|
|
|
static int qProcessProcessErrorMeta = qRegisterMetaType<QProcess::ProcessError>();
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(qProcessExitStatusMeta)
|
|
|
|
|
Q_UNUSED(qProcessProcessErrorMeta)
|
2020-09-28 16:29:51 +02:00
|
|
|
|
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) && defined(Q_OS_UNIX)
|
|
|
|
|
setChildProcessModifier([this] { setupChildProcess_impl(); });
|
|
|
|
|
#endif
|
2016-06-08 13:30:30 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
QtcProcess::~QtcProcess()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setEnvironment(const Environment &env)
|
|
|
|
|
{
|
|
|
|
|
d->m_environment = env;
|
|
|
|
|
d->m_haveEnv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Environment &QtcProcess::environment() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_environment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setCommand(const CommandLine &cmdLine)
|
|
|
|
|
{
|
|
|
|
|
d->m_commandLine = cmdLine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CommandLine &QtcProcess::commandLine() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_commandLine;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-16 15:57:02 +02:00
|
|
|
void QtcProcess::setUseCtrlCStub(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
// Do not use the stub in debug mode. Activating the stub will shut down
|
|
|
|
|
// Qt Creator otherwise, because they share the same Windows console.
|
|
|
|
|
// See QTCREATORBUG-11995 for details.
|
|
|
|
|
#ifndef QT_DEBUG
|
2021-05-05 16:05:53 +02:00
|
|
|
d->m_useCtrlCStub = enabled;
|
2014-05-20 15:17:32 +02:00
|
|
|
#else
|
|
|
|
|
Q_UNUSED(enabled)
|
2014-05-16 15:57:02 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
void QtcProcess::start()
|
|
|
|
|
{
|
2021-05-05 16:05:53 +02:00
|
|
|
if (d->m_commandLine.executable().needsDevice()) {
|
2021-04-27 15:18:58 +02:00
|
|
|
QTC_ASSERT(s_remoteRunProcessHook, return);
|
|
|
|
|
s_remoteRunProcessHook(*this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
Environment env;
|
2014-02-05 10:43:21 +01:00
|
|
|
const OsType osType = HostOsInfo::hostOs();
|
2021-05-05 16:05:53 +02:00
|
|
|
if (d->m_haveEnv) {
|
|
|
|
|
if (d->m_environment.size() == 0)
|
2019-05-28 18:59:45 +02:00
|
|
|
qWarning("QtcProcess::start: Empty environment set when running '%s'.",
|
2021-05-05 16:05:53 +02:00
|
|
|
qPrintable(d->m_commandLine.executable().toString()));
|
|
|
|
|
env = d->m_environment;
|
2011-11-23 15:21:07 +01:00
|
|
|
|
2019-08-19 14:29:14 +02:00
|
|
|
QProcess::setProcessEnvironment(env.toProcessEnvironment());
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
env = Environment::systemEnvironment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString &workDir = workingDirectory();
|
|
|
|
|
QString command;
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::Arguments arguments;
|
2021-05-05 16:05:53 +02:00
|
|
|
bool success = prepareCommand(d->m_commandLine.executable().toString(),
|
|
|
|
|
d->m_commandLine.arguments(),
|
2019-05-28 18:59:45 +02:00
|
|
|
&command, &arguments, osType, &env, &workDir);
|
2014-02-05 10:43:21 +01:00
|
|
|
if (osType == OsTypeWindows) {
|
2014-02-19 14:30:07 +01:00
|
|
|
QString args;
|
2021-05-05 16:05:53 +02:00
|
|
|
if (d->m_useCtrlCStub) {
|
|
|
|
|
if (d->m_lowPriority)
|
2019-09-10 23:39:29 +03:00
|
|
|
addArg(&args, "-nice");
|
|
|
|
|
addArg(&args, QDir::toNativeSeparators(command));
|
2014-02-19 14:30:07 +01:00
|
|
|
command = QCoreApplication::applicationDirPath()
|
|
|
|
|
+ QLatin1String("/qtcreator_ctrlc_stub.exe");
|
2021-05-05 16:05:53 +02:00
|
|
|
} else if (d->m_lowPriority) {
|
2019-09-10 23:39:29 +03:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
setCreateProcessArgumentsModifier([](CreateProcessArguments *args) {
|
|
|
|
|
args->flags |= BELOW_NORMAL_PRIORITY_CLASS;
|
|
|
|
|
});
|
|
|
|
|
#endif
|
2014-02-19 14:30:07 +01:00
|
|
|
}
|
|
|
|
|
QtcProcess::addArgs(&args, arguments.toWindowsArgs());
|
2010-10-14 18:05:43 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2014-02-05 10:43:21 +01:00
|
|
|
setNativeArguments(args);
|
2010-10-14 18:05:43 +02:00
|
|
|
#endif
|
2014-02-19 14:30:07 +01:00
|
|
|
// Note: Arguments set with setNativeArgs will be appended to the ones
|
|
|
|
|
// passed with start() below.
|
2021-05-05 16:05:53 +02:00
|
|
|
QProcess::start(command, QStringList(), d->m_openMode);
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
if (!success) {
|
|
|
|
|
setErrorString(tr("Error in command line."));
|
|
|
|
|
// Should be FailedToStart, but we cannot set the process error from the outside,
|
|
|
|
|
// so it would be inconsistent.
|
2016-08-03 19:43:54 +03:00
|
|
|
emit errorOccurred(QProcess::UnknownError);
|
2014-02-05 10:43:21 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2021-05-05 16:05:53 +02:00
|
|
|
QProcess::start(command, arguments.toUnixArgs(), d->m_openMode);
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
2021-04-29 15:57:56 +02:00
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
if (d->m_synchronous)
|
2021-04-29 15:57:56 +02:00
|
|
|
QProcess::waitForFinished();
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2013-05-03 13:52:52 +02:00
|
|
|
static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam)
|
2011-08-03 12:04:46 +02:00
|
|
|
{
|
|
|
|
|
DWORD dwProcessID;
|
|
|
|
|
GetWindowThreadProcessId(hwnd, &dwProcessID);
|
2011-08-09 13:43:56 +02:00
|
|
|
if ((DWORD)lParam == dwProcessID) {
|
2013-05-03 13:52:52 +02:00
|
|
|
SendNotifyMessage(hwnd, message, 0, 0);
|
2011-08-03 12:04:46 +02:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2013-05-03 13:52:52 +02:00
|
|
|
|
|
|
|
|
BOOL CALLBACK sendShutDownMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiShutDownMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown");
|
|
|
|
|
return sendMessage(uiShutDownMessage, hwnd, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK sendInterruptMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt");
|
|
|
|
|
return sendMessage(uiInterruptMessage, hwnd, lParam);
|
|
|
|
|
}
|
2011-08-03 12:04:46 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void QtcProcess::terminate()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
2021-05-05 16:05:53 +02:00
|
|
|
if (d->m_useCtrlCStub)
|
2020-11-02 11:19:12 +01:00
|
|
|
EnumWindows(sendShutDownMessageToAllWindowsOfProcess_enumWnd, processId());
|
2011-08-03 12:04:46 +02:00
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
QProcess::terminate();
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-03 13:52:52 +02:00
|
|
|
void QtcProcess::interrupt()
|
|
|
|
|
{
|
2013-09-12 16:34:34 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2021-05-05 16:05:53 +02:00
|
|
|
QTC_ASSERT(d->m_useCtrlCStub, return);
|
2020-11-02 11:19:12 +01:00
|
|
|
EnumWindows(sendInterruptMessageToAllWindowsOfProcess_enumWnd, processId());
|
2013-09-12 16:34:34 +02:00
|
|
|
#endif
|
2013-05-03 13:52:52 +02:00
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void QtcProcess::setLowPriority()
|
|
|
|
|
{
|
|
|
|
|
d->m_lowPriority = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setDisableUnixTerminal()
|
|
|
|
|
{
|
|
|
|
|
d->m_disableUnixTerminal = true;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
// This function assumes that the resulting string will be quoted.
|
|
|
|
|
// That's irrelevant if it does not contain quotes itself.
|
2014-02-05 10:43:21 +01:00
|
|
|
static int quoteArgInternalWin(QString &ret, int bslashes)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
// Quotes are escaped and their preceding backslashes are doubled.
|
|
|
|
|
// It's impossible to escape anything inside a quoted string on cmd
|
|
|
|
|
// level, so the outer quoting must be "suspended".
|
|
|
|
|
const QChar bs(QLatin1Char('\\')), dq(QLatin1Char('"'));
|
|
|
|
|
for (int p = 0; p < ret.length(); p++) {
|
|
|
|
|
if (ret.at(p) == bs) {
|
|
|
|
|
bslashes++;
|
|
|
|
|
} else {
|
|
|
|
|
if (ret.at(p) == dq) {
|
|
|
|
|
if (bslashes) {
|
|
|
|
|
ret.insert(p, QString(bslashes, bs));
|
|
|
|
|
p += bslashes;
|
|
|
|
|
}
|
|
|
|
|
ret.insert(p, QLatin1String("\"\\^\""));
|
|
|
|
|
p += 4;
|
|
|
|
|
}
|
|
|
|
|
bslashes = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return bslashes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: This documentation is relevant for end-users. Where to put it?
|
|
|
|
|
/**
|
|
|
|
|
* Perform safe macro expansion (substitution) on a string for use
|
|
|
|
|
* in shell commands.
|
|
|
|
|
*
|
|
|
|
|
* \section Unix notes
|
|
|
|
|
*
|
|
|
|
|
* Explicitly supported shell constructs:
|
|
|
|
|
* \\ '' "" {} () $(()) ${} $() ``
|
|
|
|
|
*
|
|
|
|
|
* Implicitly supported shell constructs:
|
|
|
|
|
* (())
|
|
|
|
|
*
|
|
|
|
|
* Unsupported shell constructs that will cause problems:
|
|
|
|
|
* \list
|
|
|
|
|
* \li Shortened \c{case $v in pat)} syntax. Use \c{case $v in (pat)} instead.
|
|
|
|
|
* \li Bash-style \c{$""} and \c{$''} string quoting syntax.
|
|
|
|
|
* \endlist
|
|
|
|
|
*
|
|
|
|
|
* The rest of the shell (incl. bash) syntax is simply ignored,
|
|
|
|
|
* as it is not expected to cause problems.
|
|
|
|
|
*
|
|
|
|
|
* Security considerations:
|
|
|
|
|
* \list
|
|
|
|
|
* \li Backslash-escaping an expando is treated as a quoting error
|
|
|
|
|
* \li Do not put expandos into double quoted substitutions:
|
|
|
|
|
* \badcode
|
|
|
|
|
* "${VAR:-%{macro}}"
|
|
|
|
|
* \endcode
|
|
|
|
|
* \li Do not put expandos into command line arguments which are nested
|
|
|
|
|
* shell commands:
|
|
|
|
|
* \badcode
|
|
|
|
|
* sh -c 'foo \%{file}'
|
|
|
|
|
* \endcode
|
|
|
|
|
* \goodcode
|
|
|
|
|
* file=\%{file} sh -c 'foo "$file"'
|
|
|
|
|
* \endcode
|
|
|
|
|
* \endlist
|
|
|
|
|
*
|
|
|
|
|
* \section Windows notes
|
|
|
|
|
*
|
|
|
|
|
* All quoting syntax supported by splitArgs() is supported here as well.
|
|
|
|
|
* Additionally, command grouping via parentheses is recognized - note
|
|
|
|
|
* however, that the parser is much stricter about unquoted parentheses
|
|
|
|
|
* than cmd itself.
|
|
|
|
|
* The rest of the cmd syntax is simply ignored, as it is not expected
|
|
|
|
|
* to cause problems.
|
|
|
|
|
*
|
|
|
|
|
* Security considerations:
|
|
|
|
|
* \list
|
|
|
|
|
* \li Circumflex-escaping an expando is treated as a quoting error
|
|
|
|
|
* \li Closing double quotes right before expandos and opening double quotes
|
|
|
|
|
* right after expandos are treated as quoting errors
|
|
|
|
|
* \li Do not put expandos into nested commands:
|
|
|
|
|
* \badcode
|
|
|
|
|
* for /f "usebackq" \%v in (`foo \%{file}`) do \@echo \%v
|
|
|
|
|
* \endcode
|
|
|
|
|
* \li A macro's value must not contain anything which may be interpreted
|
|
|
|
|
* as an environment variable expansion. A solution is replacing any
|
|
|
|
|
* percent signs with a fixed string like \c{\%PERCENT_SIGN\%} and
|
|
|
|
|
* injecting \c{PERCENT_SIGN=\%} into the shell's environment.
|
|
|
|
|
* \li Enabling delayed environment variable expansion (cmd /v:on) should have
|
|
|
|
|
* no security implications, but may still wreak havoc due to the
|
|
|
|
|
* need for doubling circumflexes if any exclamation marks are present,
|
|
|
|
|
* and the need to circumflex-escape the exclamation marks themselves.
|
|
|
|
|
* \endlist
|
|
|
|
|
*
|
|
|
|
|
* \param cmd pointer to the string in which macros are expanded in-place
|
|
|
|
|
* \param mx pointer to a macro expander instance
|
|
|
|
|
* \return false if the string could not be parsed and therefore no safe
|
|
|
|
|
* substitution was possible
|
|
|
|
|
*/
|
2014-02-05 10:43:21 +01:00
|
|
|
bool QtcProcess::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString str = *cmd;
|
|
|
|
|
if (str.isEmpty())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
QString rsts;
|
|
|
|
|
int varLen;
|
|
|
|
|
int varPos = 0;
|
|
|
|
|
if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
int pos = 0;
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
if (osType == OsTypeWindows) {
|
|
|
|
|
enum { // cmd.exe parsing state
|
|
|
|
|
ShellBasic, // initial state
|
|
|
|
|
ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
|
|
|
|
|
ShellEscaped // circumflex-escaped state => next char is not interpreted
|
|
|
|
|
} shellState = ShellBasic;
|
|
|
|
|
enum { // CommandLineToArgv() parsing state and some more
|
|
|
|
|
CrtBasic, // initial state
|
|
|
|
|
CrtNeedWord, // after empty expando; insert empty argument if whitespace follows
|
|
|
|
|
CrtInWord, // in non-whitespace
|
|
|
|
|
CrtClosed, // previous char closed the double-quoting
|
|
|
|
|
CrtHadQuote, // closed double-quoting after an expando
|
|
|
|
|
// The remaining two need to be numerically higher
|
|
|
|
|
CrtQuoted, // double-quoted state => spaces don't split tokens
|
|
|
|
|
CrtNeedQuote // expando opened quote; close if no expando follows
|
|
|
|
|
} crtState = CrtBasic;
|
|
|
|
|
int bslashes = 0; // previous chars were manual backslashes
|
|
|
|
|
int rbslashes = 0; // trailing backslashes in replacement
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
forever {
|
|
|
|
|
if (pos == varPos) {
|
|
|
|
|
if (shellState == ShellEscaped)
|
|
|
|
|
return false; // Circumflex'd quoted expando would be Bad (TM).
|
|
|
|
|
if ((shellState == ShellQuoted) != (crtState == CrtQuoted))
|
|
|
|
|
return false; // CRT quoting out of sync with shell quoting. Ahoy to Redmond.
|
|
|
|
|
rbslashes += bslashes;
|
|
|
|
|
bslashes = 0;
|
|
|
|
|
if (crtState < CrtQuoted) {
|
|
|
|
|
if (rsts.isEmpty()) {
|
|
|
|
|
if (crtState == CrtBasic) {
|
|
|
|
|
// Outside any quoting and the string is empty, so put
|
|
|
|
|
// a pair of quotes. Delaying that is just pedantry.
|
|
|
|
|
crtState = CrtNeedWord;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (hasSpecialCharsWin(rsts)) {
|
|
|
|
|
if (crtState == CrtClosed) {
|
|
|
|
|
// Quoted expando right after closing quote. Can't do that.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
int tbslashes = quoteArgInternalWin(rsts, 0);
|
|
|
|
|
rsts.prepend(QLatin1Char('"'));
|
|
|
|
|
if (rbslashes)
|
|
|
|
|
rsts.prepend(QString(rbslashes, QLatin1Char('\\')));
|
|
|
|
|
crtState = CrtNeedQuote;
|
|
|
|
|
rbslashes = tbslashes;
|
|
|
|
|
} else {
|
|
|
|
|
crtState = CrtInWord; // We know that this string contains no spaces.
|
|
|
|
|
// We know that this string contains no quotes,
|
|
|
|
|
// so the function won't make a mess.
|
|
|
|
|
rbslashes = quoteArgInternalWin(rsts, rbslashes);
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
rbslashes = quoteArgInternalWin(rsts, rbslashes);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
str.replace(pos, varLen, rsts);
|
|
|
|
|
pos += rsts.length();
|
|
|
|
|
varPos = pos;
|
|
|
|
|
if (!(varLen = mx->findMacro(str, &varPos, &rsts))) {
|
|
|
|
|
// Don't leave immediately, as we may be in CrtNeedWord state which could
|
|
|
|
|
// be still resolved, or we may have inserted trailing backslashes.
|
|
|
|
|
varPos = INT_MAX;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
if (crtState == CrtNeedQuote) {
|
|
|
|
|
if (rbslashes) {
|
|
|
|
|
str.insert(pos, QString(rbslashes, QLatin1Char('\\')));
|
|
|
|
|
pos += rbslashes;
|
|
|
|
|
varPos += rbslashes;
|
|
|
|
|
rbslashes = 0;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
str.insert(pos, QLatin1Char('"'));
|
|
|
|
|
pos++;
|
|
|
|
|
varPos++;
|
|
|
|
|
crtState = CrtHadQuote;
|
|
|
|
|
}
|
|
|
|
|
ushort cc = str.unicode()[pos].unicode();
|
|
|
|
|
if (shellState == ShellBasic && cc == '^') {
|
|
|
|
|
shellState = ShellEscaped;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (!cc || cc == ' ' || cc == '\t') {
|
|
|
|
|
if (crtState < CrtQuoted) {
|
|
|
|
|
if (crtState == CrtNeedWord) {
|
|
|
|
|
str.insert(pos, QLatin1String("\"\""));
|
|
|
|
|
pos += 2;
|
|
|
|
|
varPos += 2;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
crtState = CrtBasic;
|
|
|
|
|
}
|
|
|
|
|
if (!cc)
|
|
|
|
|
break;
|
|
|
|
|
bslashes = 0;
|
|
|
|
|
rbslashes = 0;
|
|
|
|
|
} else {
|
|
|
|
|
if (cc == '\\') {
|
|
|
|
|
bslashes++;
|
|
|
|
|
if (crtState < CrtQuoted)
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
} else {
|
|
|
|
|
if (cc == '"') {
|
|
|
|
|
if (shellState != ShellEscaped)
|
|
|
|
|
shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
|
|
|
|
|
if (rbslashes) {
|
|
|
|
|
// Offset -1: skip possible circumflex. We have at least
|
|
|
|
|
// one backslash, so a fixed offset is ok.
|
|
|
|
|
str.insert(pos - 1, QString(rbslashes, QLatin1Char('\\')));
|
|
|
|
|
pos += rbslashes;
|
|
|
|
|
varPos += rbslashes;
|
|
|
|
|
}
|
|
|
|
|
if (!(bslashes & 1)) {
|
|
|
|
|
// Even number of backslashes, so the quote is not escaped.
|
|
|
|
|
switch (crtState) {
|
|
|
|
|
case CrtQuoted:
|
|
|
|
|
// Closing quote
|
|
|
|
|
crtState = CrtClosed;
|
|
|
|
|
break;
|
|
|
|
|
case CrtClosed:
|
|
|
|
|
// Two consecutive quotes make a literal quote - and
|
|
|
|
|
// still close quoting. See QtcProcess::quoteArg().
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
break;
|
|
|
|
|
case CrtHadQuote:
|
|
|
|
|
// Opening quote right after quoted expando. Can't do that.
|
|
|
|
|
return false;
|
|
|
|
|
default:
|
|
|
|
|
// Opening quote
|
|
|
|
|
crtState = CrtQuoted;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (crtState < CrtQuoted) {
|
2010-10-14 18:05:43 +02:00
|
|
|
crtState = CrtInWord;
|
|
|
|
|
}
|
|
|
|
|
} else if (crtState < CrtQuoted) {
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
bslashes = 0;
|
|
|
|
|
rbslashes = 0;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
if (varPos == INT_MAX && !rbslashes)
|
|
|
|
|
break;
|
|
|
|
|
if (shellState == ShellEscaped)
|
|
|
|
|
shellState = ShellBasic;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
pos++;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
// !Windows
|
2017-02-22 15:09:35 +01:00
|
|
|
MxState state = {MxBasic, false};
|
2014-02-05 10:43:21 +01:00
|
|
|
QStack<MxState> sstack;
|
|
|
|
|
QStack<MxSave> ostack;
|
|
|
|
|
|
|
|
|
|
while (pos < str.length()) {
|
|
|
|
|
if (pos == varPos) {
|
|
|
|
|
// Our expansion rules trigger in any context
|
|
|
|
|
if (state.dquote) {
|
|
|
|
|
// We are within a double-quoted string. Escape relevant meta characters.
|
2020-06-11 15:23:04 +02:00
|
|
|
rsts.replace(QRegularExpression(QLatin1String("([$`\"\\\\])")), QLatin1String("\\\\1"));
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (state.current == MxSingleQuote) {
|
|
|
|
|
// We are within a single-quoted string. "Suspend" single-quoting and put a
|
|
|
|
|
// single escaped quote for each single quote inside the string.
|
|
|
|
|
rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
|
|
|
|
|
} else if (rsts.isEmpty() || hasSpecialCharsUnix(rsts)) {
|
|
|
|
|
// String contains "quote-worthy" characters. Use single quoting - but
|
|
|
|
|
// that choice is arbitrary.
|
|
|
|
|
rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
|
|
|
|
|
rsts.prepend(QLatin1Char('\''));
|
|
|
|
|
rsts.append(QLatin1Char('\''));
|
|
|
|
|
} // Else just use the string verbatim.
|
|
|
|
|
str.replace(pos, varLen, rsts);
|
|
|
|
|
pos += rsts.length();
|
|
|
|
|
varPos = pos;
|
|
|
|
|
if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
|
2010-10-14 18:05:43 +02:00
|
|
|
break;
|
2014-02-05 10:43:21 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
ushort cc = str.unicode()[pos].unicode();
|
|
|
|
|
if (state.current == MxSingleQuote) {
|
|
|
|
|
// Single quoted context - only the single quote has any special meaning.
|
|
|
|
|
if (cc == '\'')
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else if (cc == '\\') {
|
|
|
|
|
// In any other context, the backslash starts an escape.
|
|
|
|
|
pos += 2;
|
|
|
|
|
if (varPos < pos)
|
|
|
|
|
return false; // Backslash'd quoted expando would be Bad (TM).
|
|
|
|
|
continue;
|
|
|
|
|
} else if (cc == '$') {
|
|
|
|
|
cc = str.unicode()[++pos].unicode();
|
|
|
|
|
if (cc == '(') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
if (str.unicode()[pos + 1].unicode() == '(') {
|
|
|
|
|
// $(( starts a math expression. This may also be a $( ( in fact,
|
|
|
|
|
// so we push the current string and offset on a stack so we can retry.
|
2017-02-22 15:09:35 +01:00
|
|
|
MxSave sav = {str, pos + 2, varPos};
|
2014-02-05 10:43:21 +01:00
|
|
|
ostack.push(sav);
|
|
|
|
|
state.current = MxMath;
|
|
|
|
|
pos += 2;
|
2010-10-14 18:05:43 +02:00
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
// $( starts a command substitution. This actually "opens a new context"
|
|
|
|
|
// which overrides surrounding double quoting.
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '{') {
|
|
|
|
|
// ${ starts a "braced" variable substitution.
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxSubst;
|
|
|
|
|
} // Else assume that a "bare" variable substitution has started
|
|
|
|
|
} else if (cc == '`') {
|
|
|
|
|
// Backticks are evil, as every shell interprets escapes within them differently,
|
|
|
|
|
// which is a danger for the quoting of our own expansions.
|
|
|
|
|
// So we just apply *our* rules (which match bash) and transform it into a POSIX
|
|
|
|
|
// command substitution which has clear semantics.
|
|
|
|
|
str.replace(pos, 1, QLatin1String("$( " )); // add space -> avoid creating $((
|
|
|
|
|
varPos += 2;
|
|
|
|
|
int pos2 = pos += 3;
|
|
|
|
|
forever {
|
|
|
|
|
if (pos2 >= str.length())
|
|
|
|
|
return false; // Syntax error - unterminated backtick expression.
|
|
|
|
|
cc = str.unicode()[pos2].unicode();
|
|
|
|
|
if (cc == '`')
|
|
|
|
|
break;
|
|
|
|
|
if (cc == '\\') {
|
|
|
|
|
cc = str.unicode()[++pos2].unicode();
|
|
|
|
|
if (cc == '$' || cc == '`' || cc == '\\' ||
|
|
|
|
|
(cc == '"' && state.dquote))
|
|
|
|
|
{
|
|
|
|
|
str.remove(pos2 - 1, 1);
|
|
|
|
|
if (varPos >= pos2)
|
|
|
|
|
varPos--;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
pos2++;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
str[pos2] = QLatin1Char(')');
|
2010-10-14 18:05:43 +02:00
|
|
|
sstack.push(state);
|
2014-02-05 10:43:21 +01:00
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
continue;
|
|
|
|
|
} else if (state.current == MxDoubleQuote) {
|
|
|
|
|
// (Truly) double quoted context - only remaining special char is the closing quote.
|
|
|
|
|
if (cc == '"')
|
2010-10-14 18:05:43 +02:00
|
|
|
state = sstack.pop();
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '\'') {
|
|
|
|
|
// Start single quote if we are not in "inherited" double quoted context.
|
|
|
|
|
if (!state.dquote) {
|
2010-10-14 18:05:43 +02:00
|
|
|
sstack.push(state);
|
2014-02-05 10:43:21 +01:00
|
|
|
state.current = MxSingleQuote;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '"') {
|
|
|
|
|
// Same for double quoting.
|
|
|
|
|
if (!state.dquote) {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxDoubleQuote;
|
|
|
|
|
state.dquote = true;
|
|
|
|
|
}
|
|
|
|
|
} else if (state.current == MxSubst) {
|
|
|
|
|
// "Braced" substitution context - only remaining special char is the closing brace.
|
|
|
|
|
if (cc == '}')
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else if (cc == ')') {
|
|
|
|
|
if (state.current == MxMath) {
|
|
|
|
|
if (str.unicode()[pos + 1].unicode() == ')') {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
pos += 2;
|
|
|
|
|
} else {
|
|
|
|
|
// False hit: the $(( was a $( ( in fact.
|
|
|
|
|
// ash does not care (and will complain), but bash actually parses it.
|
|
|
|
|
varPos = ostack.top().varPos;
|
|
|
|
|
pos = ostack.top().pos;
|
|
|
|
|
str = ostack.top().str;
|
|
|
|
|
ostack.pop();
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else if (state.current == MxParen) {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else {
|
|
|
|
|
break; // Syntax error - excess closing parenthesis.
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '}') {
|
|
|
|
|
if (state.current == MxGroup)
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
else
|
|
|
|
|
break; // Syntax error - excess closing brace.
|
|
|
|
|
} else if (cc == '(') {
|
|
|
|
|
// Context-saving command grouping.
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
} else if (cc == '{') {
|
|
|
|
|
// Plain command grouping.
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxGroup;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
pos++;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
// FIXME? May complain if (!sstack.empty()), but we don't really care anyway.
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*cmd = str;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QString QtcProcess::expandMacros(const QString &str, AbstractMacroExpander *mx, OsType osType)
|
2010-10-14 18:05:43 +02:00
|
|
|
{
|
|
|
|
|
QString ret = str;
|
2014-02-05 10:43:21 +01:00
|
|
|
expandMacros(&ret, mx, osType);
|
2010-10-14 18:05:43 +02:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 15:18:58 +02:00
|
|
|
void QtcProcess::setRemoteStartProcessHook(const std::function<void(QtcProcess &)> &hook)
|
|
|
|
|
{
|
|
|
|
|
s_remoteRunProcessHook = hook;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 16:29:51 +02:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
2020-06-11 14:59:40 +02:00
|
|
|
void QtcProcess::setupChildProcess()
|
2020-09-28 16:29:51 +02:00
|
|
|
{
|
2021-05-05 16:05:53 +02:00
|
|
|
d->setupChildProcess_impl();
|
2020-09-28 16:29:51 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void QtcProcessPrivate::setupChildProcess_impl()
|
2020-04-21 10:25:54 -07:00
|
|
|
{
|
2020-06-11 14:59:40 +02:00
|
|
|
#if defined Q_OS_UNIX
|
2019-09-10 23:39:29 +03:00
|
|
|
// nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest
|
|
|
|
|
if (m_lowPriority) {
|
|
|
|
|
errno = 0;
|
|
|
|
|
if (::nice(5) == -1 && errno != 0)
|
2020-02-18 15:33:15 -08:00
|
|
|
perror("Failed to set nice value");
|
2019-09-10 23:39:29 +03:00
|
|
|
}
|
2021-05-05 15:40:43 +02:00
|
|
|
|
|
|
|
|
// Disable terminal by becoming a session leader.
|
|
|
|
|
if (m_disableUnixTerminal)
|
|
|
|
|
setsid();
|
2020-06-11 14:59:40 +02:00
|
|
|
#endif
|
2019-09-10 23:39:29 +03:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 15:57:56 +02:00
|
|
|
bool QtcProcess::isSynchronous() const
|
|
|
|
|
{
|
2021-05-05 16:05:53 +02:00
|
|
|
return d->m_synchronous;
|
2021-04-29 15:57:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setSynchronous(bool on)
|
|
|
|
|
{
|
2021-05-05 16:05:53 +02:00
|
|
|
d->m_synchronous = on;
|
2021-04-29 15:57:56 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 17:15:48 +02:00
|
|
|
void QtcProcess::setOpenMode(OpenMode mode)
|
|
|
|
|
{
|
2021-05-05 16:05:53 +02:00
|
|
|
d->m_openMode = mode;
|
2021-04-29 17:15:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::stopProcess()
|
|
|
|
|
{
|
|
|
|
|
if (state() == QProcess::NotRunning)
|
|
|
|
|
return true;
|
|
|
|
|
terminate();
|
|
|
|
|
if (waitForFinished(300))
|
|
|
|
|
return true;
|
|
|
|
|
kill();
|
|
|
|
|
return waitForFinished(300);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-30 12:31:36 +02:00
|
|
|
static bool askToKill(const QString &command)
|
|
|
|
|
{
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
if (QThread::currentThread() != QCoreApplication::instance()->thread())
|
|
|
|
|
return true;
|
|
|
|
|
const QString title = QtcProcess::tr("Process not Responding");
|
|
|
|
|
QString msg = command.isEmpty() ?
|
|
|
|
|
QtcProcess::tr("The process is not responding.") :
|
|
|
|
|
QtcProcess::tr("The process \"%1\" is not responding.").arg(command);
|
|
|
|
|
msg += ' ';
|
|
|
|
|
msg += QtcProcess::tr("Would you like to terminate it?");
|
|
|
|
|
// Restore the cursor that is set to wait while running.
|
|
|
|
|
const bool hasOverrideCursor = QApplication::overrideCursor() != nullptr;
|
|
|
|
|
if (hasOverrideCursor)
|
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
|
QMessageBox::StandardButton answer = QMessageBox::question(nullptr, title, msg, QMessageBox::Yes|QMessageBox::No);
|
|
|
|
|
if (hasOverrideCursor)
|
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
|
return answer == QMessageBox::Yes;
|
|
|
|
|
#else
|
|
|
|
|
Q_UNUSED(command)
|
|
|
|
|
return true;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 07:49:33 +02:00
|
|
|
// Helper for running a process synchronously in the foreground with timeout
|
|
|
|
|
// detection similar SynchronousProcess' handling (taking effect after no more output
|
|
|
|
|
// occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout
|
|
|
|
|
// occurs. Checking of the process' exit state/code still has to be done.
|
|
|
|
|
|
2021-04-30 12:31:36 +02:00
|
|
|
bool QtcProcess::readDataFromProcess(int timeoutS,
|
|
|
|
|
QByteArray *stdOut,
|
|
|
|
|
QByteArray *stdErr,
|
|
|
|
|
bool showTimeOutMessageBox)
|
|
|
|
|
{
|
|
|
|
|
enum { syncDebug = 0 };
|
|
|
|
|
if (syncDebug)
|
|
|
|
|
qDebug() << ">readDataFromProcess" << timeoutS;
|
|
|
|
|
if (state() != QProcess::Running) {
|
|
|
|
|
qWarning("readDataFromProcess: Process in non-running state passed in.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(readChannel() == QProcess::StandardOutput, return false);
|
|
|
|
|
|
|
|
|
|
// Keep the process running until it has no longer has data
|
|
|
|
|
bool finished = false;
|
|
|
|
|
bool hasData = false;
|
|
|
|
|
do {
|
|
|
|
|
finished = waitForFinished(timeoutS > 0 ? timeoutS * 1000 : -1)
|
|
|
|
|
|| state() == QProcess::NotRunning;
|
|
|
|
|
// First check 'stdout'
|
|
|
|
|
if (bytesAvailable()) { // applies to readChannel() only
|
|
|
|
|
hasData = true;
|
|
|
|
|
const QByteArray newStdOut = readAllStandardOutput();
|
|
|
|
|
if (stdOut)
|
|
|
|
|
stdOut->append(newStdOut);
|
|
|
|
|
}
|
|
|
|
|
// Check 'stderr' separately. This is a special handling
|
|
|
|
|
// for 'git pull' and the like which prints its progress on stderr.
|
|
|
|
|
const QByteArray newStdErr = readAllStandardError();
|
|
|
|
|
if (!newStdErr.isEmpty()) {
|
|
|
|
|
hasData = true;
|
|
|
|
|
if (stdErr)
|
|
|
|
|
stdErr->append(newStdErr);
|
|
|
|
|
}
|
|
|
|
|
// Prompt user, pretend we have data if says 'No'.
|
|
|
|
|
const bool hang = !hasData && !finished;
|
|
|
|
|
hasData = hang && showTimeOutMessageBox && !askToKill(program());
|
|
|
|
|
} while (hasData && !finished);
|
|
|
|
|
if (syncDebug)
|
|
|
|
|
qDebug() << "<readDataFromProcess" << finished;
|
|
|
|
|
return finished;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
bool QtcProcess::ArgIterator::next()
|
|
|
|
|
{
|
|
|
|
|
// We delay the setting of m_prev so we can still delete the last argument
|
|
|
|
|
// after we find that there are no more arguments. It's a bit of a hack ...
|
|
|
|
|
int prev = m_pos;
|
|
|
|
|
|
|
|
|
|
m_simple = true;
|
|
|
|
|
m_value.clear();
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
if (m_osType == OsTypeWindows) {
|
|
|
|
|
enum { // cmd.exe parsing state
|
|
|
|
|
ShellBasic, // initial state
|
|
|
|
|
ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
|
|
|
|
|
ShellEscaped // circumflex-escaped state => next char is not interpreted
|
|
|
|
|
} shellState = ShellBasic;
|
|
|
|
|
enum { // CommandLineToArgv() parsing state and some more
|
|
|
|
|
CrtBasic, // initial state
|
|
|
|
|
CrtInWord, // in non-whitespace
|
|
|
|
|
CrtClosed, // previous char closed the double-quoting
|
|
|
|
|
CrtQuoted // double-quoted state => spaces don't split tokens
|
|
|
|
|
} crtState = CrtBasic;
|
|
|
|
|
enum { NoVar, NewVar, FullVar } varState = NoVar; // inside a potential env variable expansion
|
|
|
|
|
int bslashes = 0; // number of preceding backslashes
|
|
|
|
|
|
|
|
|
|
for (;; m_pos++) {
|
|
|
|
|
ushort cc = m_pos < m_str->length() ? m_str->unicode()[m_pos].unicode() : 0;
|
|
|
|
|
if (shellState == ShellBasic && cc == '^') {
|
|
|
|
|
varState = NoVar;
|
|
|
|
|
shellState = ShellEscaped;
|
|
|
|
|
} else if ((shellState == ShellBasic && isMetaCharWin(cc)) || !cc) { // A "bit" simplistic ...
|
|
|
|
|
// We ignore crtQuote state here. Whatever ...
|
|
|
|
|
doReturn:
|
|
|
|
|
if (m_simple)
|
|
|
|
|
while (--bslashes >= 0)
|
|
|
|
|
m_value += QLatin1Char('\\');
|
|
|
|
|
else
|
|
|
|
|
m_value.clear();
|
2010-10-14 18:05:43 +02:00
|
|
|
if (crtState != CrtBasic) {
|
2014-02-05 10:43:21 +01:00
|
|
|
m_prev = prev;
|
|
|
|
|
return true;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
return false;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (crtState != CrtQuoted && (cc == ' ' || cc == '\t')) {
|
|
|
|
|
if (crtState != CrtBasic) {
|
|
|
|
|
// We'll lose shellQuote state here. Whatever ...
|
|
|
|
|
goto doReturn;
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (cc == '\\') {
|
|
|
|
|
bslashes++;
|
|
|
|
|
if (crtState != CrtQuoted)
|
2010-10-14 18:05:43 +02:00
|
|
|
crtState = CrtInWord;
|
2014-02-05 10:43:21 +01:00
|
|
|
varState = NoVar;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
2014-02-05 10:43:21 +01:00
|
|
|
if (cc == '"') {
|
|
|
|
|
varState = NoVar;
|
|
|
|
|
if (shellState != ShellEscaped)
|
|
|
|
|
shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
|
|
|
|
|
int obslashes = bslashes;
|
|
|
|
|
bslashes >>= 1;
|
|
|
|
|
if (!(obslashes & 1)) {
|
|
|
|
|
// Even number of backslashes, so the quote is not escaped.
|
|
|
|
|
switch (crtState) {
|
|
|
|
|
case CrtQuoted:
|
|
|
|
|
// Closing quote
|
|
|
|
|
crtState = CrtClosed;
|
|
|
|
|
continue;
|
|
|
|
|
case CrtClosed:
|
|
|
|
|
// Two consecutive quotes make a literal quote - and
|
|
|
|
|
// still close quoting. See quoteArg().
|
|
|
|
|
crtState = CrtInWord;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// Opening quote
|
|
|
|
|
crtState = CrtQuoted;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (crtState != CrtQuoted) {
|
|
|
|
|
crtState = CrtInWord;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
|
|
|
|
if (cc == '%') {
|
|
|
|
|
if (varState == FullVar) {
|
|
|
|
|
m_simple = false;
|
|
|
|
|
varState = NoVar;
|
|
|
|
|
} else {
|
|
|
|
|
varState = NewVar;
|
|
|
|
|
}
|
|
|
|
|
} else if (varState != NoVar) {
|
|
|
|
|
// This check doesn't really reflect cmd reality, but it is an
|
|
|
|
|
// approximation of what would be sane.
|
|
|
|
|
varState = (cc == '_' || cc == '-' || cc == '.'
|
|
|
|
|
|| QChar(cc).isLetterOrNumber()) ? FullVar : NoVar;
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
}
|
|
|
|
|
if (crtState != CrtQuoted)
|
|
|
|
|
crtState = CrtInWord;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
for (; bslashes > 0; bslashes--)
|
|
|
|
|
m_value += QLatin1Char('\\');
|
|
|
|
|
m_value += QChar(cc);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
if (shellState == ShellEscaped)
|
|
|
|
|
shellState = ShellBasic;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
} else {
|
2017-02-22 15:09:35 +01:00
|
|
|
MxState state = {MxBasic, false};
|
2014-02-05 10:43:21 +01:00
|
|
|
QStack<MxState> sstack;
|
|
|
|
|
QStack<int> ostack;
|
|
|
|
|
bool hadWord = false;
|
|
|
|
|
|
|
|
|
|
for (; m_pos < m_str->length(); m_pos++) {
|
|
|
|
|
ushort cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (state.current == MxSingleQuote) {
|
|
|
|
|
if (cc == '\'') {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '\\') {
|
|
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
|
|
|
|
cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (state.dquote && cc != '"' && cc != '\\' && cc != '$' && cc != '`')
|
|
|
|
|
m_value += QLatin1Char('\\');
|
|
|
|
|
} else if (cc == '$') {
|
2010-10-14 18:05:43 +02:00
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
2014-02-05 10:43:21 +01:00
|
|
|
cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (cc == '(') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
|
|
|
|
if (m_str->unicode()[m_pos].unicode() == '(') {
|
|
|
|
|
ostack.push(m_pos);
|
|
|
|
|
state.current = MxMath;
|
|
|
|
|
} else {
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
// m_pos too far by one now - whatever.
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '{') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxSubst;
|
2010-10-14 18:05:43 +02:00
|
|
|
} else {
|
|
|
|
|
// m_pos too far by one now - whatever.
|
|
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
m_simple = false;
|
2010-10-14 18:05:43 +02:00
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '`') {
|
|
|
|
|
forever {
|
|
|
|
|
if (++m_pos >= m_str->length()) {
|
|
|
|
|
m_simple = false;
|
|
|
|
|
m_prev = prev;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
cc = m_str->unicode()[m_pos].unicode();
|
|
|
|
|
if (cc == '`')
|
|
|
|
|
break;
|
|
|
|
|
if (cc == '\\')
|
|
|
|
|
m_pos++; // m_pos may be too far by one now - whatever.
|
|
|
|
|
}
|
|
|
|
|
m_simple = false;
|
2010-10-14 18:05:43 +02:00
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (state.current == MxDoubleQuote) {
|
|
|
|
|
if (cc == '"') {
|
2010-10-14 18:05:43 +02:00
|
|
|
state = sstack.pop();
|
2014-02-05 10:43:21 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '\'') {
|
|
|
|
|
if (!state.dquote) {
|
2010-10-14 18:05:43 +02:00
|
|
|
sstack.push(state);
|
2014-02-05 10:43:21 +01:00
|
|
|
state.current = MxSingleQuote;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (cc == '"') {
|
|
|
|
|
if (!state.dquote) {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.dquote = true;
|
|
|
|
|
state.current = MxDoubleQuote;
|
|
|
|
|
hadWord = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else if (state.current == MxSubst) {
|
|
|
|
|
if (cc == '}')
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue; // Not simple anyway
|
|
|
|
|
} else if (cc == ')') {
|
|
|
|
|
if (state.current == MxMath) {
|
|
|
|
|
if (++m_pos >= m_str->length())
|
|
|
|
|
break;
|
|
|
|
|
if (m_str->unicode()[m_pos].unicode() == ')') {
|
|
|
|
|
ostack.pop();
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
} else {
|
|
|
|
|
// false hit: the $(( was a $( ( in fact.
|
|
|
|
|
// ash does not care, but bash does.
|
|
|
|
|
m_pos = ostack.pop();
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
state.dquote = false;
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else if (state.current == MxParen) {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
#if 0 // MxGroup is impossible, see below.
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '}') {
|
|
|
|
|
if (state.current == MxGroup) {
|
|
|
|
|
state = sstack.pop();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
#endif
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '(') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxParen;
|
|
|
|
|
m_simple = false;
|
|
|
|
|
hadWord = true;
|
2015-03-07 20:22:32 +02:00
|
|
|
continue;
|
2010-10-14 18:05:43 +02:00
|
|
|
#if 0 // Should match only at the beginning of a command, which we never have currently.
|
2014-02-05 10:43:21 +01:00
|
|
|
} else if (cc == '{') {
|
|
|
|
|
sstack.push(state);
|
|
|
|
|
state.current = MxGroup;
|
|
|
|
|
m_simple = false;
|
|
|
|
|
hadWord = true;
|
2010-10-14 18:05:43 +02:00
|
|
|
continue;
|
2014-02-05 10:43:21 +01:00
|
|
|
#endif
|
|
|
|
|
} else if (cc == '<' || cc == '>' || cc == '&' || cc == '|' || cc == ';') {
|
|
|
|
|
if (sstack.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
} else if (cc == ' ' || cc == '\t') {
|
|
|
|
|
if (!hadWord)
|
|
|
|
|
continue;
|
|
|
|
|
if (sstack.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
m_value += QChar(cc);
|
|
|
|
|
hadWord = true;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
2014-02-05 10:43:21 +01:00
|
|
|
// TODO: Possibly complain here if (!sstack.empty())
|
|
|
|
|
if (!m_simple)
|
|
|
|
|
m_value.clear();
|
|
|
|
|
if (hadWord) {
|
|
|
|
|
m_prev = prev;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-30 17:21:43 +02:00
|
|
|
QString QtcProcess::normalizeNewlines(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
QString res = text;
|
|
|
|
|
const auto newEnd = std::unique(res.begin(), res.end(), [](const QChar &c1, const QChar &c2) {
|
|
|
|
|
return c1 == '\r' && c2 == '\r'; // QTCREATORBUG-24556
|
|
|
|
|
});
|
|
|
|
|
res.chop(std::distance(newEnd, res.end()));
|
|
|
|
|
res.replace("\r\n", "\n");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
void QtcProcess::ArgIterator::deleteArg()
|
|
|
|
|
{
|
|
|
|
|
if (!m_prev)
|
|
|
|
|
while (m_pos < m_str->length() && m_str->at(m_pos).isSpace())
|
|
|
|
|
m_pos++;
|
|
|
|
|
m_str->remove(m_prev, m_pos - m_prev);
|
|
|
|
|
m_pos = m_prev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::ArgIterator::appendArg(const QString &str)
|
|
|
|
|
{
|
|
|
|
|
const QString qstr = quoteArg(str);
|
|
|
|
|
if (!m_pos)
|
|
|
|
|
m_str->insert(0, qstr + QLatin1Char(' '));
|
|
|
|
|
else
|
|
|
|
|
m_str->insert(m_pos, QLatin1Char(' ') + qstr);
|
|
|
|
|
m_pos += qstr.length() + 1;
|
|
|
|
|
}
|
2013-07-12 09:00:12 +02:00
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
QtcProcess::Arguments QtcProcess::Arguments::createWindowsArgs(const QString &args)
|
|
|
|
|
{
|
|
|
|
|
Arguments result;
|
|
|
|
|
result.m_windowsArgs = args;
|
|
|
|
|
result.m_isWindows = true;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QtcProcess::Arguments QtcProcess::Arguments::createUnixArgs(const QStringList &args)
|
|
|
|
|
{
|
|
|
|
|
Arguments result;
|
|
|
|
|
result.m_unixArgs = args;
|
|
|
|
|
result.m_isWindows = false;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::Arguments::toWindowsArgs() const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_isWindows);
|
|
|
|
|
return m_windowsArgs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList QtcProcess::Arguments::toUnixArgs() const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(!m_isWindows);
|
|
|
|
|
return m_unixArgs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::Arguments::toString() const
|
|
|
|
|
{
|
|
|
|
|
if (m_isWindows)
|
|
|
|
|
return m_windowsArgs;
|
|
|
|
|
else
|
|
|
|
|
return QtcProcess::joinArgs(m_unixArgs, OsTypeLinux);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 15:04:30 +02:00
|
|
|
// Path utilities
|
|
|
|
|
|
|
|
|
|
// Locate a binary in a directory, applying all kinds of
|
|
|
|
|
// extensions the operating system supports.
|
|
|
|
|
static QString checkBinary(const QDir &dir, const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
// naive UNIX approach
|
|
|
|
|
const QFileInfo info(dir.filePath(binary));
|
|
|
|
|
if (info.isFile() && info.isExecutable())
|
|
|
|
|
return info.absoluteFilePath();
|
|
|
|
|
|
|
|
|
|
// Does the OS have some weird extension concept or does the
|
|
|
|
|
// binary have a 3 letter extension?
|
|
|
|
|
if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
|
|
|
|
|
return QString();
|
|
|
|
|
const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
|
|
|
|
|
if (dotIndex != -1 && dotIndex == binary.size() - 4)
|
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
|
|
switch (HostOsInfo::hostOs()) {
|
|
|
|
|
case OsTypeLinux:
|
|
|
|
|
case OsTypeOtherUnix:
|
|
|
|
|
case OsTypeOther:
|
|
|
|
|
break;
|
|
|
|
|
case OsTypeWindows: {
|
|
|
|
|
static const char *windowsExtensions[] = {".cmd", ".bat", ".exe", ".com"};
|
|
|
|
|
// Check the Windows extensions using the order
|
|
|
|
|
const int windowsExtensionCount = sizeof(windowsExtensions)/sizeof(const char*);
|
|
|
|
|
for (int e = 0; e < windowsExtensionCount; e ++) {
|
|
|
|
|
const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e])));
|
|
|
|
|
if (windowsBinary.isFile() && windowsBinary.isExecutable())
|
|
|
|
|
return windowsBinary.absoluteFilePath();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case OsTypeMac: {
|
|
|
|
|
// Check for Mac app folders
|
|
|
|
|
const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app")));
|
|
|
|
|
if (appFolder.isDir()) {
|
|
|
|
|
QString macBinaryPath = appFolder.absoluteFilePath();
|
|
|
|
|
macBinaryPath += QLatin1String("/Contents/MacOS/");
|
|
|
|
|
macBinaryPath += binary;
|
|
|
|
|
const QFileInfo macBinary(macBinaryPath);
|
|
|
|
|
if (macBinary.isFile() && macBinary.isExecutable())
|
|
|
|
|
return macBinary.absoluteFilePath();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::locateBinary(const QString &path, const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
// Absolute file?
|
|
|
|
|
const QFileInfo absInfo(binary);
|
|
|
|
|
if (absInfo.isAbsolute())
|
|
|
|
|
return checkBinary(absInfo.dir(), absInfo.fileName());
|
|
|
|
|
|
|
|
|
|
// Windows finds binaries in the current directory
|
|
|
|
|
if (HostOsInfo::isWindowsHost()) {
|
|
|
|
|
const QString currentDirBinary = checkBinary(QDir::current(), binary);
|
|
|
|
|
if (!currentDirBinary.isEmpty())
|
|
|
|
|
return currentDirBinary;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QStringList paths = path.split(HostOsInfo::pathListSeparator());
|
|
|
|
|
if (paths.empty())
|
|
|
|
|
return QString();
|
|
|
|
|
const QStringList::const_iterator cend = paths.constEnd();
|
|
|
|
|
for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) {
|
|
|
|
|
const QDir dir(*it);
|
|
|
|
|
const QString rc = checkBinary(dir, binary);
|
|
|
|
|
if (!rc.isEmpty())
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::locateBinary(const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
const QByteArray path = qgetenv("PATH");
|
|
|
|
|
return locateBinary(QString::fromLocal8Bit(path), binary);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
\class Utils::SynchronousProcess
|
|
|
|
|
|
|
|
|
|
\brief The SynchronousProcess class runs a synchronous process in its own
|
|
|
|
|
event loop that blocks only user input events. Thus, it allows for the GUI to
|
|
|
|
|
repaint and append output to log windows.
|
|
|
|
|
|
2021-05-06 08:56:42 +02:00
|
|
|
The callbacks set with setStdOutCallBack(), setStdErrCallback() are called
|
|
|
|
|
with complete lines based on the '\\n' marker.
|
2021-05-05 18:21:22 +02:00
|
|
|
They would typically be used for log windows.
|
|
|
|
|
|
|
|
|
|
There is a timeout handling that takes effect after the last data have been
|
|
|
|
|
read from stdout/stdin (as opposed to waitForFinished(), which measures time
|
|
|
|
|
since it was invoked). It is thus also suitable for slow processes that
|
|
|
|
|
continuously output data (like version system operations).
|
|
|
|
|
|
|
|
|
|
The property timeOutMessageBoxEnabled influences whether a message box is
|
|
|
|
|
shown asking the user if they want to kill the process on timeout (default: false).
|
|
|
|
|
|
|
|
|
|
There are also static utility functions for dealing with fully synchronous
|
|
|
|
|
processes, like reading the output with correct timeout handling.
|
|
|
|
|
|
|
|
|
|
Caution: This class should NOT be used if there is a chance that the process
|
|
|
|
|
triggers opening dialog boxes (for example, by file watchers triggering),
|
|
|
|
|
as this will cause event loop problems.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// ----------- SynchronousProcessResponse
|
|
|
|
|
void SynchronousProcessResponse::clear()
|
|
|
|
|
{
|
|
|
|
|
result = StartFailed;
|
|
|
|
|
exitCode = -1;
|
|
|
|
|
rawStdOut.clear();
|
|
|
|
|
rawStdErr.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutS) const
|
|
|
|
|
{
|
|
|
|
|
switch (result) {
|
|
|
|
|
case Finished:
|
2021-05-06 11:15:28 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" finished successfully.").arg(QDir::toNativeSeparators(binary));
|
2021-05-05 18:21:22 +02:00
|
|
|
case FinishedError:
|
2021-05-06 11:15:28 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" terminated with exit code %2.").arg(QDir::toNativeSeparators(binary)).arg(exitCode);
|
2021-05-05 18:21:22 +02:00
|
|
|
case TerminatedAbnormally:
|
2021-05-06 11:15:28 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" terminated abnormally.").arg(QDir::toNativeSeparators(binary));
|
2021-05-05 18:21:22 +02:00
|
|
|
case StartFailed:
|
2021-05-06 11:15:28 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" could not be started.").arg(QDir::toNativeSeparators(binary));
|
2021-05-05 18:21:22 +02:00
|
|
|
case Hang:
|
2021-05-06 11:15:28 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" did not respond within the timeout limit (%2 s).")
|
2021-05-05 18:21:22 +02:00
|
|
|
.arg(QDir::toNativeSeparators(binary)).arg(timeoutS);
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray SynchronousProcessResponse::allRawOutput() const
|
|
|
|
|
{
|
|
|
|
|
if (!rawStdOut.isEmpty() && !rawStdErr.isEmpty()) {
|
|
|
|
|
QByteArray result = rawStdOut;
|
|
|
|
|
if (!result.endsWith('\n'))
|
|
|
|
|
result += '\n';
|
|
|
|
|
result += rawStdErr;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
return !rawStdOut.isEmpty() ? rawStdOut : rawStdErr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcessResponse::allOutput() const
|
|
|
|
|
{
|
|
|
|
|
const QString out = stdOut();
|
|
|
|
|
const QString err = stdErr();
|
|
|
|
|
|
|
|
|
|
if (!out.isEmpty() && !err.isEmpty()) {
|
|
|
|
|
QString result = out;
|
|
|
|
|
if (!result.endsWith('\n'))
|
|
|
|
|
result += '\n';
|
|
|
|
|
result += err;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
return !out.isEmpty() ? out : err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcessResponse::stdOut() const
|
|
|
|
|
{
|
|
|
|
|
return QtcProcess::normalizeNewlines(codec->toUnicode(rawStdOut));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcessResponse::stdErr() const
|
|
|
|
|
{
|
|
|
|
|
return QtcProcess::normalizeNewlines(codec->toUnicode(rawStdErr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r)
|
|
|
|
|
{
|
|
|
|
|
QDebug nsp = str.nospace();
|
|
|
|
|
nsp << "SynchronousProcessResponse: result=" << r.result << " ex=" << r.exitCode << '\n'
|
|
|
|
|
<< r.rawStdOut.size() << " bytes stdout, stderr=" << r.rawStdErr << '\n';
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SynchronousProcessResponse::Result defaultExitCodeInterpreter(int code)
|
|
|
|
|
{
|
|
|
|
|
return code ? SynchronousProcessResponse::FinishedError
|
|
|
|
|
: SynchronousProcessResponse::Finished;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChannelBuffer::clearForRun()
|
|
|
|
|
{
|
|
|
|
|
rawDataPos = 0;
|
|
|
|
|
rawData.clear();
|
|
|
|
|
codecState.reset(new QTextCodec::ConverterState);
|
|
|
|
|
incompleteLineBuffer.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for complete lines read from the device and return them, moving the
|
|
|
|
|
* buffer position. */
|
|
|
|
|
QString ChannelBuffer::linesRead()
|
|
|
|
|
{
|
|
|
|
|
// Convert and append the new input to the buffer of incomplete lines
|
|
|
|
|
const char *start = rawData.constData() + rawDataPos;
|
|
|
|
|
const int len = rawData.size() - rawDataPos;
|
|
|
|
|
incompleteLineBuffer.append(codec->toUnicode(start, len, codecState.get()));
|
|
|
|
|
rawDataPos = rawData.size();
|
|
|
|
|
|
|
|
|
|
// Any completed lines in the incompleteLineBuffer?
|
|
|
|
|
const int lastLineIndex = qMax(incompleteLineBuffer.lastIndexOf('\n'),
|
|
|
|
|
incompleteLineBuffer.lastIndexOf('\r'));
|
|
|
|
|
if (lastLineIndex == -1)
|
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
|
|
// Get completed lines and remove them from the incompleteLinesBuffer:
|
|
|
|
|
const QString lines = QtcProcess::normalizeNewlines(incompleteLineBuffer.left(lastLineIndex + 1));
|
|
|
|
|
incompleteLineBuffer = incompleteLineBuffer.mid(lastLineIndex + 1);
|
|
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChannelBuffer::append(const QByteArray &text, bool emitSignals)
|
|
|
|
|
{
|
|
|
|
|
if (text.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
rawData += text;
|
|
|
|
|
if (!emitSignals)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Buffered. Emit complete lines?
|
2021-05-06 08:56:42 +02:00
|
|
|
if (outputCallback) {
|
2021-05-05 18:21:22 +02:00
|
|
|
const QString lines = linesRead();
|
2021-05-06 08:56:42 +02:00
|
|
|
if (!lines.isEmpty())
|
|
|
|
|
outputCallback(lines);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ----------- SynchronousProcess
|
2021-05-06 11:15:28 +02:00
|
|
|
SynchronousProcess::SynchronousProcess()
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
d->m_timer.setInterval(1000);
|
2021-05-06 11:15:28 +02:00
|
|
|
connect(&d->m_timer, &QTimer::timeout, d, &QtcProcessPrivate::slotTimeout);
|
2021-05-06 10:06:45 +02:00
|
|
|
connect(this, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
2021-05-06 11:15:28 +02:00
|
|
|
d, &QtcProcessPrivate::slotFinished);
|
|
|
|
|
connect(this, &QProcess::errorOccurred, d, &QtcProcessPrivate::slotError);
|
|
|
|
|
connect(this, &QProcess::readyReadStandardOutput, d, [this] {
|
2021-05-06 10:06:45 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2021-05-06 11:15:28 +02:00
|
|
|
d->processStdOut(true);
|
2021-05-06 10:06:45 +02:00
|
|
|
});
|
2021-05-06 11:15:28 +02:00
|
|
|
connect(this, &QProcess::readyReadStandardError, d, [this] {
|
2021-05-06 10:06:45 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2021-05-06 11:15:28 +02:00
|
|
|
d->processStdErr(true);
|
2021-05-06 10:06:45 +02:00
|
|
|
});
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SynchronousProcess::~SynchronousProcess()
|
|
|
|
|
{
|
|
|
|
|
disconnect(&d->m_timer, nullptr, this, nullptr);
|
2021-05-06 10:06:45 +02:00
|
|
|
disconnect(this, nullptr, this, nullptr);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setTimeoutS(int timeoutS)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
if (timeoutS > 0)
|
|
|
|
|
d->m_maxHangTimerCount = qMax(2, timeoutS);
|
|
|
|
|
else
|
|
|
|
|
d->m_maxHangTimerCount = INT_MAX / 1000;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setCodec(QTextCodec *c)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(c, return);
|
|
|
|
|
d->m_codec = c;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setTimeOutMessageBoxEnabled(bool v)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
d->m_timeOutMessageBoxEnabled = v;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(interpreter, return);
|
|
|
|
|
d->m_exitCodeInterpreter = interpreter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
static bool isGuiThread()
|
|
|
|
|
{
|
|
|
|
|
return QThread::currentThread() == QCoreApplication::instance()->thread();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
SynchronousProcessResponse QtcProcess::run(const CommandLine &cmd, const QByteArray &writeData)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
// FIXME: Implement properly
|
|
|
|
|
if (cmd.executable().needsDevice()) {
|
|
|
|
|
QtcProcess proc;
|
2021-05-06 10:06:45 +02:00
|
|
|
proc.setEnvironment(Environment(environment()));
|
|
|
|
|
proc.setWorkingDirectory(workingDirectory());
|
2021-05-05 18:21:22 +02:00
|
|
|
proc.setCommand(cmd);
|
|
|
|
|
|
|
|
|
|
// writeData ?
|
|
|
|
|
proc.start();
|
|
|
|
|
|
|
|
|
|
proc.waitForFinished();
|
|
|
|
|
|
|
|
|
|
SynchronousProcessResponse res;
|
|
|
|
|
res.result = SynchronousProcessResponse::Finished;
|
|
|
|
|
res.exitCode = proc.exitCode();
|
|
|
|
|
res.rawStdOut = proc.readAllStandardOutput();
|
|
|
|
|
res.rawStdErr = proc.readAllStandardError();
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
qCDebug(processLog).noquote() << "Starting:" << cmd.toUserOutput();
|
|
|
|
|
ExecuteOnDestruction logResult([this] {
|
|
|
|
|
qCDebug(processLog) << d->m_result;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
d->clearForRun();
|
|
|
|
|
|
|
|
|
|
d->m_binary = cmd.executable();
|
|
|
|
|
// using QProcess::start() and passing program, args and OpenMode results in a different
|
|
|
|
|
// quoting of arguments than using QProcess::setArguments() beforehand and calling start()
|
|
|
|
|
// only with the OpenMode
|
2021-05-06 10:06:45 +02:00
|
|
|
setCommand(cmd);
|
2021-05-05 18:21:22 +02:00
|
|
|
if (!writeData.isEmpty()) {
|
2021-05-06 10:06:45 +02:00
|
|
|
connect(this, &QProcess::started, this, [this, writeData] {
|
|
|
|
|
write(writeData);
|
|
|
|
|
closeWriteChannel();
|
2021-05-05 18:21:22 +02:00
|
|
|
});
|
|
|
|
|
}
|
2021-05-06 10:06:45 +02:00
|
|
|
setOpenMode(writeData.isEmpty() ? QIODevice::ReadOnly : QIODevice::ReadWrite);
|
|
|
|
|
start();
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
// On Windows, start failure is triggered immediately if the
|
|
|
|
|
// executable cannot be found in the path. Do not start the
|
|
|
|
|
// event loop in that case.
|
|
|
|
|
if (!d->m_startFailure) {
|
|
|
|
|
d->m_timer.start();
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
if (isGuiThread())
|
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
|
#endif
|
|
|
|
|
d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
|
2021-05-06 11:15:28 +02:00
|
|
|
d->processStdOut(false);
|
|
|
|
|
d->processStdErr(false);
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
d->m_result.rawStdOut = d->m_stdOut.rawData;
|
|
|
|
|
d->m_result.rawStdErr = d->m_stdErr.rawData;
|
|
|
|
|
|
|
|
|
|
d->m_timer.stop();
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
if (isGuiThread())
|
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return d->m_result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
SynchronousProcessResponse QtcProcess::runBlocking(const CommandLine &cmd)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
// FIXME: Implement properly
|
|
|
|
|
if (cmd.executable().needsDevice()) {
|
|
|
|
|
QtcProcess proc;
|
2021-05-06 10:06:45 +02:00
|
|
|
proc.setEnvironment(Environment(environment()));
|
|
|
|
|
proc.setWorkingDirectory(workingDirectory());
|
2021-05-05 18:21:22 +02:00
|
|
|
proc.setCommand(cmd);
|
|
|
|
|
|
|
|
|
|
// writeData ?
|
|
|
|
|
proc.start();
|
|
|
|
|
|
|
|
|
|
proc.waitForFinished();
|
|
|
|
|
|
|
|
|
|
SynchronousProcessResponse res;
|
|
|
|
|
res.result = SynchronousProcessResponse::Finished;
|
|
|
|
|
res.exitCode = proc.exitCode();
|
|
|
|
|
res.rawStdOut = proc.readAllStandardOutput();
|
|
|
|
|
res.rawStdErr = proc.readAllStandardError();
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
qCDebug(processLog).noquote() << "Starting blocking:" << cmd.toUserOutput();
|
|
|
|
|
ExecuteOnDestruction logResult([this] {
|
|
|
|
|
qCDebug(processLog) << d->m_result;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
d->clearForRun();
|
|
|
|
|
|
|
|
|
|
d->m_binary = cmd.executable();
|
2021-05-06 10:06:45 +02:00
|
|
|
setOpenMode(QIODevice::ReadOnly);
|
|
|
|
|
setCommand(cmd);
|
|
|
|
|
start();
|
|
|
|
|
if (!waitForStarted(d->m_maxHangTimerCount * 1000)) {
|
2021-05-05 18:21:22 +02:00
|
|
|
d->m_result.result = SynchronousProcessResponse::StartFailed;
|
|
|
|
|
return d->m_result;
|
|
|
|
|
}
|
2021-05-06 10:06:45 +02:00
|
|
|
closeWriteChannel();
|
|
|
|
|
if (!waitForFinished(d->m_maxHangTimerCount * 1000)) {
|
2021-05-05 18:21:22 +02:00
|
|
|
d->m_result.result = SynchronousProcessResponse::Hang;
|
2021-05-06 10:06:45 +02:00
|
|
|
terminate();
|
|
|
|
|
if (!waitForFinished(1000)) {
|
|
|
|
|
kill();
|
|
|
|
|
waitForFinished(1000);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 10:06:45 +02:00
|
|
|
if (state() != QProcess::NotRunning)
|
2021-05-05 18:21:22 +02:00
|
|
|
return d->m_result;
|
|
|
|
|
|
2021-05-06 10:06:45 +02:00
|
|
|
d->m_result.exitCode = exitCode();
|
2021-05-05 18:21:22 +02:00
|
|
|
if (d->m_result.result == SynchronousProcessResponse::StartFailed) {
|
2021-05-06 10:06:45 +02:00
|
|
|
if (exitStatus() != QProcess::NormalExit)
|
2021-05-05 18:21:22 +02:00
|
|
|
d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally;
|
|
|
|
|
else
|
2021-05-06 10:06:45 +02:00
|
|
|
d->m_result.result = d->m_exitCodeInterpreter(d->m_result.exitCode);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
2021-05-06 11:15:28 +02:00
|
|
|
d->processStdOut(false);
|
|
|
|
|
d->processStdErr(false);
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
d->m_result.rawStdOut = d->m_stdOut.rawData;
|
|
|
|
|
d->m_result.rawStdErr = d->m_stdErr.rawData;
|
|
|
|
|
|
|
|
|
|
return d->m_result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setStdOutCallback(const std::function<void (const QString &)> &callback)
|
2021-05-06 08:56:42 +02:00
|
|
|
{
|
|
|
|
|
d->m_stdOut.outputCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setStdErrCallback(const std::function<void (const QString &)> &callback)
|
2021-05-06 08:56:42 +02:00
|
|
|
{
|
|
|
|
|
d->m_stdErr.outputCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::slotTimeout()
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-05-06 11:15:28 +02:00
|
|
|
if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) {
|
2021-05-05 18:21:22 +02:00
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << "HANG detected, killing";
|
2021-05-06 11:15:28 +02:00
|
|
|
m_waitingForUser = true;
|
|
|
|
|
const bool terminate = !m_timeOutMessageBoxEnabled || askToKill(m_binary.toString());
|
|
|
|
|
m_waitingForUser = false;
|
2021-05-05 18:21:22 +02:00
|
|
|
if (terminate) {
|
2021-05-06 11:15:28 +02:00
|
|
|
q->stopProcess();
|
|
|
|
|
m_result.result = SynchronousProcessResponse::Hang;
|
2021-05-05 18:21:22 +02:00
|
|
|
} else {
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (debug)
|
2021-05-06 11:15:28 +02:00
|
|
|
qDebug() << Q_FUNC_INFO << m_hangTimerCount;
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::slotFinished(int exitCode, QProcess::ExitStatus e)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << exitCode << e;
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
switch (e) {
|
|
|
|
|
case QProcess::NormalExit:
|
2021-05-06 11:15:28 +02:00
|
|
|
m_result.result = m_exitCodeInterpreter(exitCode);
|
|
|
|
|
m_result.exitCode = exitCode;
|
2021-05-05 18:21:22 +02:00
|
|
|
break;
|
|
|
|
|
case QProcess::CrashExit:
|
|
|
|
|
// Was hang detected before and killed?
|
2021-05-06 11:15:28 +02:00
|
|
|
if (m_result.result != SynchronousProcessResponse::Hang)
|
|
|
|
|
m_result.result = SynchronousProcessResponse::TerminatedAbnormally;
|
|
|
|
|
m_result.exitCode = -1;
|
2021-05-05 18:21:22 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2021-05-06 11:15:28 +02:00
|
|
|
m_eventLoop.quit();
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::slotError(QProcess::ProcessError e)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << e;
|
|
|
|
|
// Was hang detected before and killed?
|
2021-05-06 11:15:28 +02:00
|
|
|
if (m_result.result != SynchronousProcessResponse::Hang)
|
|
|
|
|
m_result.result = SynchronousProcessResponse::StartFailed;
|
|
|
|
|
m_startFailure = true;
|
|
|
|
|
m_eventLoop.quit();
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::processStdOut(bool emitSignals)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
// Handle binary data
|
2021-05-06 11:15:28 +02:00
|
|
|
m_stdOut.append(q->readAllStandardOutput(), emitSignals);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::processStdErr(bool emitSignals)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
// Handle binary data
|
2021-05-06 11:15:28 +02:00
|
|
|
m_stdErr.append(q->readAllStandardError(), emitSignals);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 09:00:12 +02:00
|
|
|
} // namespace Utils
|