qt6windows7/qmake/library/ioutils.cpp
2023-10-29 23:33:08 +01:00

301 lines
9.7 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "ioutils.h"
#include <qdir.h>
#include <qfile.h>
#include <qregularexpression.h>
#ifdef Q_OS_WIN
# include <qt_windows.h>
# include <private/qsystemerror_p.h>
#else
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <utime.h>
# include <fcntl.h>
# include <errno.h>
#endif
#define fL1S(s) QString::fromLatin1(s)
QT_BEGIN_NAMESPACE
using namespace QMakeInternal;
QString IoUtils::binaryAbsLocation(const QString &argv0)
{
QString ret;
if (!argv0.isEmpty() && isAbsolutePath(argv0)) {
ret = argv0;
} else if (argv0.contains(QLatin1Char('/'))
#ifdef Q_OS_WIN
|| argv0.contains(QLatin1Char('\\'))
#endif
) { // relative PWD
ret = QDir::current().absoluteFilePath(argv0);
} else { // in the PATH
QByteArray pEnv = qgetenv("PATH");
QDir currentDir = QDir::current();
#ifdef Q_OS_WIN
QStringList paths = QString::fromLocal8Bit(pEnv).split(QLatin1String(";"));
paths.prepend(QLatin1String("."));
#else
QStringList paths = QString::fromLocal8Bit(pEnv).split(QLatin1String(":"));
#endif
for (QStringList::const_iterator p = paths.constBegin(); p != paths.constEnd(); ++p) {
if ((*p).isEmpty())
continue;
QString candidate = currentDir.absoluteFilePath(*p + QLatin1Char('/') + argv0);
if (QFile::exists(candidate)) {
ret = candidate;
break;
}
}
}
return QDir::cleanPath(ret);
}
IoUtils::FileType IoUtils::fileType(const QString &fileName)
{
Q_ASSERT(fileName.isEmpty() || isAbsolutePath(fileName));
#ifdef Q_OS_WIN
DWORD attr = GetFileAttributesW((WCHAR*)fileName.utf16());
if (attr == INVALID_FILE_ATTRIBUTES)
return FileNotFound;
return (attr & FILE_ATTRIBUTE_DIRECTORY) ? FileIsDir : FileIsRegular;
#else
struct ::stat st;
if (::stat(fileName.toLocal8Bit().constData(), &st))
return FileNotFound;
return S_ISDIR(st.st_mode) ? FileIsDir : S_ISREG(st.st_mode) ? FileIsRegular : FileNotFound;
#endif
}
bool IoUtils::isRelativePath(const QString &path)
{
#ifdef QMAKE_BUILTIN_PRFS
if (path.startsWith(QLatin1String(":/")))
return false;
#endif
#ifdef Q_OS_WIN
// Unlike QFileInfo, this considers only paths with both a drive prefix and
// a subsequent (back-)slash absolute:
if (path.length() >= 3 && path.at(1) == QLatin1Char(':') && path.at(0).isLetter()
&& (path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\'))) {
return false;
}
// ... unless, of course, they're UNC:
if (path.length() >= 2
&& (path.at(0).unicode() == '\\' || path.at(0).unicode() == '/')
&& path.at(1) == path.at(0)) {
return false;
}
#else
if (path.startsWith(QLatin1Char('/')))
return false;
#endif // Q_OS_WIN
return true;
}
QStringView IoUtils::pathName(const QString &fileName)
{
return QStringView{fileName}.left(fileName.lastIndexOf(QLatin1Char('/')) + 1);
}
QStringView IoUtils::fileName(const QString &fileName)
{
return QStringView(fileName).mid(fileName.lastIndexOf(QLatin1Char('/')) + 1);
}
QString IoUtils::resolvePath(const QString &baseDir, const QString &fileName)
{
if (fileName.isEmpty())
return QString();
if (isAbsolutePath(fileName))
return QDir::cleanPath(fileName);
#ifdef Q_OS_WIN // Add drive to otherwise-absolute path:
if (fileName.at(0).unicode() == '/' || fileName.at(0).unicode() == '\\') {
Q_ASSERT_X(isAbsolutePath(baseDir), "IoUtils::resolvePath", qUtf8Printable(baseDir));
return QDir::cleanPath(baseDir.left(2) + fileName);
}
#endif // Q_OS_WIN
return QDir::cleanPath(baseDir + QLatin1Char('/') + fileName);
}
inline static
bool isSpecialChar(ushort c, const uchar (&iqm)[16])
{
if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
return true;
return false;
}
inline static
bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
{
for (int x = arg.size() - 1; x >= 0; --x) {
if (isSpecialChar(arg.unicode()[x].unicode(), iqm))
return true;
}
return false;
}
QString IoUtils::shellQuoteUnix(const QString &arg)
{
// 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 \'"$`<>|;&(){}*?#!~[]
if (!arg.size())
return QString::fromLatin1("''");
QString ret(arg);
if (hasSpecialChars(ret, iqm)) {
ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
ret.prepend(QLatin1Char('\''));
ret.append(QLatin1Char('\''));
}
return ret;
}
QString IoUtils::shellQuoteWin(const QString &arg)
{
// 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
};
// Shell meta chars that need escaping.
static const uchar ism[] = {
0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
}; // &()<>^|
if (!arg.size())
return QString::fromLatin1("\"\"");
QString ret(arg);
if (hasSpecialChars(ret, iqm)) {
// The process-level standard quoting allows escaping quotes with backslashes (note
// that backslashes don't escape themselves, unless they are followed by a quote).
// Consequently, quotes are escaped and their preceding backslashes are doubled.
ret.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\""));
// Trailing backslashes must be doubled as well, as they are followed by a quote.
ret.replace(QRegularExpression(QLatin1String("(\\\\+)$")), QLatin1String("\\1\\1"));
// However, the shell also interprets the command, and no backslash-escaping exists
// there - a quote always toggles the quoting state, but is nonetheless passed down
// to the called process verbatim. In the unquoted state, the circumflex escapes
// meta chars (including itself and quotes), and is removed from the command.
bool quoted = true;
for (int i = 0; i < ret.size(); i++) {
QChar c = ret.unicode()[i];
if (c.unicode() == '"')
quoted = !quoted;
else if (!quoted && isSpecialChar(c.unicode(), ism))
ret.insert(i++, QLatin1Char('^'));
}
if (!quoted)
ret.append(QLatin1Char('^'));
ret.append(QLatin1Char('"'));
ret.prepend(QLatin1Char('"'));
}
return ret;
}
#if defined(PROEVALUATOR_FULL)
bool IoUtils::touchFile(const QString &targetFileName, const QString &referenceFileName, QString *errorString)
{
# ifdef Q_OS_UNIX
struct stat st;
if (stat(referenceFileName.toLocal8Bit().constData(), &st)) {
*errorString = fL1S("Cannot stat() reference file %1: %2.").arg(referenceFileName, fL1S(strerror(errno)));
return false;
}
# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L
const struct timespec times[2] = { { 0, UTIME_NOW }, st.st_mtim };
const bool utimeError = utimensat(AT_FDCWD, targetFileName.toLocal8Bit().constData(), times, 0) < 0;
# else
struct utimbuf utb;
utb.actime = time(0);
utb.modtime = st.st_mtime;
const bool utimeError= utime(targetFileName.toLocal8Bit().constData(), &utb) < 0;
# endif
if (utimeError) {
*errorString = fL1S("Cannot touch %1: %2.").arg(targetFileName, fL1S(strerror(errno)));
return false;
}
# else
HANDLE rHand = CreateFile((wchar_t*)referenceFileName.utf16(),
GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (rHand == INVALID_HANDLE_VALUE) {
*errorString = fL1S("Cannot open reference file %1: %2")
.arg(referenceFileName, QSystemError::windowsString());
return false;
}
FILETIME ft;
GetFileTime(rHand, NULL, NULL, &ft);
CloseHandle(rHand);
HANDLE wHand = CreateFile((wchar_t*)targetFileName.utf16(),
GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (wHand == INVALID_HANDLE_VALUE) {
*errorString = fL1S("Cannot open %1: %2")
.arg(targetFileName, QSystemError::windowsString());
return false;
}
SetFileTime(wHand, NULL, NULL, &ft);
CloseHandle(wHand);
# endif
return true;
}
#if defined(QT_BUILD_QMAKE) && defined(Q_OS_UNIX)
bool IoUtils::readLinkTarget(const QString &symlinkPath, QString *target)
{
const QByteArray localSymlinkPath = QFile::encodeName(symlinkPath);
# if defined(__GLIBC__) && !defined(PATH_MAX)
# define PATH_CHUNK_SIZE 256
char *s = 0;
int len = -1;
int size = PATH_CHUNK_SIZE;
forever {
s = (char *)::realloc(s, size);
len = ::readlink(localSymlinkPath.constData(), s, size);
if (len < 0) {
::free(s);
break;
}
if (len < size)
break;
size *= 2;
}
# else
char s[PATH_MAX+1];
int len = readlink(localSymlinkPath.constData(), s, PATH_MAX);
# endif
if (len <= 0)
return false;
*target = QFile::decodeName(QByteArray(s, len));
# if defined(__GLIBC__) && !defined(PATH_MAX)
::free(s);
# endif
return true;
}
#endif
#endif // PROEVALUATOR_FULL
QT_END_NAMESPACE