Commit 938fb0af authored by Oswald Buddenhagen's avatar Oswald Buddenhagen
Browse files

do not change current working directory while evaluating files

this makes the i/o part of the evaluator thread-safe.
for safety, assert absolute paths in IoUtils::exists().

on the way, i "stole" some code i wrote for KDE. there have been no
copyright-worthy contributions from others to it, so this is legal.
parent b309f944
......@@ -44,6 +44,7 @@ using namespace ProFileEvaluatorInternal;
IoUtils::FileType IoUtils::fileType(const QString &fileName)
{
Q_ASSERT(fileName.isEmpty() || isAbsolutePath(fileName));
#ifdef Q_OS_WIN
DWORD attr = GetFileAttributesW((WCHAR*)fileName.constData());
if (attr == INVALID_FILE_ATTRIBUTES)
......@@ -86,3 +87,116 @@ QString IoUtils::resolvePath(const QString &baseDir, const QString &fileName)
return QDir::cleanPath(fileName);
return QDir::cleanPath(baseDir + QLatin1Char('/') + fileName);
}
#ifdef Q_OS_WIN
// FIXME: Without this, quoting is not foolproof. But it needs support in the process setup, etc.
//#define PERCENT_ESCAPE QLatin1String("%PERCENT_SIGN%")
static QString quoteArgInternal(const QString &arg)
{
// Escape quotes, preceding backslashes are doubled. Surround with quotes.
// Note that cmd does not understand quote escapes in quoted strings,
// so the quoting needs to be "suspended".
const QLatin1Char bs('\\'), dq('"');
QString ret;
bool inquote = false;
int bslashes = 0;
for (int p = 0; p < arg.length(); p++) {
if (arg[p] == bs) {
bslashes++;
} else if (arg[p] == dq) {
if (inquote) {
ret.append(dq);
inquote = false;
}
for (; bslashes; bslashes--)
ret.append(QLatin1String("\\\\"));
ret.append(QLatin1String("\\^\""));
} else {
if (!inquote) {
ret.append(dq);
inquote = true;
}
for (; bslashes; bslashes--)
ret.append(bs);
ret.append(arg[p]);
}
}
//ret.replace(QLatin1Char('%'), PERCENT_ESCAPE);
if (bslashes) {
// Ensure that we don't have directly trailing backslashes,
// so concatenating with another string won't cause surprises.
if (!inquote)
ret.append(dq);
for (; bslashes; bslashes--)
ret.append(QLatin1String("\\\\"));
ret.append(dq);
} else if (inquote) {
ret.append(dq);
}
return ret;
}
inline static bool isSpecialChar(ushort c)
{
// 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, 0x41, 0x13, 0x00, 0x78,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
};
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
}
QString IoUtils::shellQuote(const QString &arg)
{
if (arg.isEmpty())
return QString::fromLatin1("\"\"");
// Ensure that we don't have directly trailing backslashes,
// so concatenating with another string won't cause surprises.
if (arg.endsWith(QLatin1Char('\\')))
return quoteArgInternal(arg);
for (int x = arg.length() - 1; x >= 0; --x)
if (isSpecialChar(arg[x].unicode()))
return quoteArgInternal(arg);
// Escape quotes. Preceding backslashes are doubled.
// Note that the remaining string is not quoted.
QString ret(arg);
ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\^\""));
//ret.replace('%', PERCENT_ESCAPE);
return ret;
}
#else // Q_OS_WIN
inline static bool isSpecial(const QChar &cUnicode)
{
static const uchar iqm[] = {
0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
}; // 0-32 \'"$`<>|;&(){}*?#!~[]
uint c = cUnicode.unicode();
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
}
QString IoUtils::shellQuote(const QString &arg)
{
if (!arg.length())
return QString::fromLatin1("''");
for (int i = 0; i < arg.length(); i++)
if (isSpecial(arg.unicode()[i])) {
const QLatin1Char q('\'');
return q + QString(arg).replace(q, QLatin1String("'\\''")) + q;
}
return arg;
}
#endif // Q_OS_WIN
......@@ -52,6 +52,7 @@ public:
static bool isAbsolutePath(const QString &fileName) { return !isRelativePath(fileName); }
static QStringRef fileName(const QString &fileName); // Requires normalized path
static QString resolvePath(const QString &baseDir, const QString &fileName);
static QString shellQuote(const QString &arg);
};
}
......
......@@ -282,7 +282,6 @@ public:
bool m_hadCondition; // Nested calls set it on return, so no need for it to be in State
int m_skipLevel;
bool m_cumulative;
QStack<QString> m_oldPathStack; // To restore the current path to the path
QStack<ProFile*> m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri'
struct ProLoop {
QString variable;
......@@ -1051,12 +1050,6 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitProFile(ProFile *pro)
{
m_lineNo = pro->lineNumber();
m_oldPathStack.push(QDir::currentPath());
if (!QDir::setCurrent(pro->directoryName())) {
m_oldPathStack.pop();
return ProItem::ReturnFalse;
}
m_profileStack.push(pro);
if (m_profileStack.count() == 1) {
// Do this only for the initial profile we visit, since
......@@ -1114,7 +1107,8 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitProFile(ProFile *pro)
}
if (IoUtils::isRelativePath(qmakespec)) {
if (IoUtils::exists(qmakespec + QLatin1String("/qmake.conf"))) {
if (IoUtils::exists(currentDirectory() + QLatin1Char('/') + qmakespec
+ QLatin1String("/qmake.conf"))) {
qmakespec = currentDirectory() + QLatin1Char('/') + qmakespec;
} else if (!m_outputDir.isEmpty()
&& IoUtils::exists(m_outputDir + QLatin1Char('/') + qmakespec
......@@ -1199,7 +1193,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitProFile(ProFile *pro)
}
m_profileStack.pop();
return returnBool(QDir::setCurrent(m_oldPathStack.pop()));
return ProItem::ReturnTrue;
}
ProItem::ProItemReturn ProFileEvaluator::Private::visitProFunction(ProFunction *func)
......@@ -1927,7 +1921,7 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun
if (args.count() > 1)
singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive));
QFile qfile(file);
QFile qfile(resolvePath(file));
if (qfile.open(QIODevice::ReadOnly)) {
QTextStream stream(&qfile);
while (!stream.atEnd()) {
......@@ -1981,7 +1975,9 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun
logMessage(format("system(execute) requires one or two arguments."));
} else {
char buff[256];
FILE *proc = QT_POPEN(args[0].toLatin1(), "r");
FILE *proc = QT_POPEN((QLatin1String("cd ")
+ IoUtils::shellQuote(currentDirectory())
+ QLatin1String(" && ") + args[0]).toLatin1(), "r");
bool singleLine = true;
if (args.count() > 1)
singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive));
......@@ -2068,20 +2064,20 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun
if (args.count() == 2)
recursive = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive) || args[1].toInt());
QStringList dirs;
QString r = fixPathToLocalOS(args[0]);
QString r = fixPathToLocalOS(resolvePath(args[0]));
int slash = r.lastIndexOf(QDir::separator());
if (slash != -1) {
dirs.append(r.left(slash));
r = r.mid(slash+1);
} else {
dirs.append(QString());
dirs.append(fixPathToLocalOS(currentDirectory()));
}
const QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard);
for (int d = 0; d < dirs.count(); d++) {
QString dir = dirs[d];
if (!dir.isEmpty() && !dir.endsWith(m_option->dir_sep))
dir += QLatin1Char('/');
if (!dir.endsWith(QDir::separator()))
dir += QDir::separator();
QDir qdir(dir);
for (int i = 0; i < (int)qdir.count(); ++i) {
......@@ -2617,7 +2613,9 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction(
logMessage(format("system(exec) requires one argument."));
ProItem::ReturnFalse;
}
return returnBool(system(args.first().toLatin1().constData()) == 0);
return returnBool(system((QLatin1String("cd ")
+ IoUtils::shellQuote(currentDirectory())
+ QLatin1String(" && ") + args.first()).toLatin1().constData()) == 0);
}
#endif
case T_ISEMPTY: {
......@@ -2641,7 +2639,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction(
return ProItem::ReturnFalse;
}
QString file = args.first();
file = fixPathToLocalOS(file);
file = fixPathToLocalOS(resolvePath(file));
if (IoUtils::exists(file)) {
return ProItem::ReturnTrue;
......@@ -2871,7 +2869,7 @@ bool ProFileEvaluator::Private::evaluateFeatureFile(
if (!fn.endsWith(QLatin1String(".prf")))
fn += QLatin1String(".prf");
if (!fileName.contains((ushort)'/') || !IoUtils::exists(fn)) {
if (!fileName.contains((ushort)'/') || !IoUtils::exists(resolvePath(fn))) {
if (m_option->feature_roots.isEmpty())
m_option->feature_roots = qmakeFeaturePaths();
int start_root = 0;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment