Darwin: Teach QFileSystemEngine how to resolve case-sensitivity
Both APFS and HFS+ can be both case-sensitive and case-insensitive (the default), and the mounted file system may be any other file system than these two as well, so hard-coding to case-sensitive is not sufficient. Pick-to: 6.8 Task-number: QTBUG-28246 Task-number: QTBUG-31103 Change-Id: Ibdb902df3f169b016a519f67ad5a79e6afb6aae3 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
7d6bac5f2b
commit
3d08816f4c
@ -27,6 +27,8 @@
|
||||
# include "qmutex.h"
|
||||
#endif
|
||||
|
||||
#include <private/qorderedmutexlocker_p.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdlib.h>
|
||||
@ -1828,7 +1830,12 @@ bool comparesEqual(const QDir &lhs, const QDir &rhs)
|
||||
if (d->fileEngine.get() != other->fileEngine.get()) // one is native, the other is a custom file-engine
|
||||
return false;
|
||||
|
||||
sensitive = QFileSystemEngine::isCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
QOrderedMutexLocker locker(&d->fileCache.mutex, &other->fileCache.mutex);
|
||||
const bool thisCaseSensitive = QFileSystemEngine::isCaseSensitive(d->dirEntry, d->fileCache.metaData);
|
||||
if (thisCaseSensitive != QFileSystemEngine::isCaseSensitive(other->dirEntry, other->fileCache.metaData))
|
||||
return false;
|
||||
|
||||
sensitive = thisCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
} else {
|
||||
if (d->fileEngine->caseSensitive() != other->fileEngine->caseSensitive())
|
||||
return false;
|
||||
|
@ -463,7 +463,11 @@ bool comparesEqual(const QFileInfo &lhs, const QFileInfo &rhs)
|
||||
if (lhs.d_ptr->fileEngine != rhs.d_ptr->fileEngine) // one is native, the other is a custom file-engine
|
||||
return false;
|
||||
|
||||
sensitive = QFileSystemEngine::isCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
const bool lhsCaseSensitive = QFileSystemEngine::isCaseSensitive(lhs.d_ptr->fileEntry, lhs.d_ptr->metaData);
|
||||
if (lhsCaseSensitive != QFileSystemEngine::isCaseSensitive(rhs.d_ptr->fileEntry, rhs.d_ptr->metaData))
|
||||
return false;
|
||||
|
||||
sensitive = lhsCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
} else {
|
||||
if (lhs.d_ptr->fileEngine->caseSensitive() != rhs.d_ptr->fileEngine->caseSensitive())
|
||||
return false;
|
||||
|
@ -23,6 +23,7 @@ class Q_CORE_EXPORT QFileInfo
|
||||
{
|
||||
friend class QDirIteratorPrivate;
|
||||
friend class QDirListingPrivate;
|
||||
friend class QFileInfoPrivate;
|
||||
public:
|
||||
explicit QFileInfo(QFileInfoPrivate *d);
|
||||
|
||||
|
@ -45,6 +45,8 @@ public:
|
||||
CachedPerms = 0x100
|
||||
};
|
||||
|
||||
static QFileInfoPrivate *get(QFileInfo *fi) { return fi->d_func(); }
|
||||
|
||||
inline QFileInfoPrivate()
|
||||
: QSharedData(), fileEngine(nullptr),
|
||||
cachedFlags(0),
|
||||
|
@ -58,14 +58,10 @@ inline bool qIsFilenameBroken(const QFileSystemEntry &entry)
|
||||
class Q_AUTOTEST_EXPORT QFileSystemEngine
|
||||
{
|
||||
public:
|
||||
static bool isCaseSensitive()
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#ifndef QT_BUILD_INTERNAL
|
||||
Q_CORE_EXPORT
|
||||
#endif
|
||||
}
|
||||
static bool isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &data);
|
||||
|
||||
static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data);
|
||||
static QFileSystemEntry getRawLinkPath(const QFileSystemEntry &link,
|
||||
|
@ -874,7 +874,7 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM
|
||||
Q_CHECK_FILE_NAME(entry, false);
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
if (what & QFileSystemMetaData::BundleType) {
|
||||
if (what & (QFileSystemMetaData::BundleType | QFileSystemMetaData::CaseSensitive)) {
|
||||
if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
|
||||
what |= QFileSystemMetaData::DirectoryType;
|
||||
}
|
||||
@ -1029,6 +1029,13 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM
|
||||
|
||||
data.knownFlagsMask |= QFileSystemMetaData::BundleType;
|
||||
}
|
||||
|
||||
if (what & QFileSystemMetaData::CaseSensitive) {
|
||||
if (entryErrno == 0 && hasResourcePropertyFlag(
|
||||
data, entry, kCFURLVolumeSupportsCaseSensitiveNamesKey))
|
||||
data.entryFlags |= QFileSystemMetaData::CaseSensitive;
|
||||
data.knownFlagsMask |= QFileSystemMetaData::CaseSensitive;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (what & QFileSystemMetaData::HiddenAttribute
|
||||
@ -1838,4 +1845,19 @@ QFileSystemEntry QFileSystemEngine::currentPath()
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
|
||||
{
|
||||
#if defined(Q_OS_DARWIN)
|
||||
if (!metaData.hasFlags(QFileSystemMetaData::CaseSensitive))
|
||||
fillMetaData(entry, metaData, QFileSystemMetaData::CaseSensitive);
|
||||
return metaData.entryFlags.testFlag(QFileSystemMetaData::CaseSensitive);
|
||||
#else
|
||||
Q_UNUSED(entry);
|
||||
Q_UNUSED(metaData);
|
||||
// FIXME: This may not be accurate for all file systems (QTBUG-28246)
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -1861,6 +1861,12 @@ bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &, QFileSystemMetaData &)
|
||||
{
|
||||
// FIXME: This may not be accurate for all file systems (QTBUG-28246)
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline QDateTime fileTimeToQDateTime(const FILETIME *time)
|
||||
{
|
||||
if (time->dwHighDateTime == 0 && time->dwLowDateTime == 0)
|
||||
|
@ -111,6 +111,8 @@ public:
|
||||
UserId = 0x10000000,
|
||||
GroupId = 0x20000000,
|
||||
|
||||
CaseSensitive = 0x80000000,
|
||||
|
||||
OwnerIds = UserId | GroupId,
|
||||
|
||||
PosixStatFlags = QFileSystemMetaData::OtherPermissions
|
||||
|
@ -863,10 +863,6 @@ bool QFSFileEngine::supportsExtension(Extension extension) const
|
||||
return false;
|
||||
}
|
||||
|
||||
/*! \fn bool QFSFileEngine::caseSensitive() const
|
||||
Returns \c false for Windows, true for Unix.
|
||||
*/
|
||||
|
||||
/*! \fn QString QFSFileEngine::currentPath(const QString &fileName)
|
||||
For Unix, returns the current working directory for the file
|
||||
engine.
|
||||
@ -1040,6 +1036,16 @@ bool QFSFileEngine::setCurrentPath(const QString &path)
|
||||
return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path));
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the file system considers the file name to be
|
||||
case sensitive.
|
||||
*/
|
||||
bool QFSFileEngine::caseSensitive() const
|
||||
{
|
||||
Q_D(const QFSFileEngine);
|
||||
return QFileSystemEngine::isCaseSensitive(d->fileEntry, d->metaData);
|
||||
}
|
||||
|
||||
/*! \fn bool QFSFileEngine::setPermissions(uint perms)
|
||||
\reimp
|
||||
*/
|
||||
|
@ -294,11 +294,6 @@ qint64 QFSFileEnginePrivate::nativeSize() const
|
||||
return sizeFdFh();
|
||||
}
|
||||
|
||||
bool QFSFileEngine::caseSensitive() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QString QFSFileEngine::currentPath(const QString &)
|
||||
{
|
||||
return QFileSystemEngine::currentPath().filePath();
|
||||
|
@ -425,11 +425,6 @@ bool QFSFileEnginePrivate::nativeRenameOverwrite(const QFileSystemEntry &newEntr
|
||||
return res;
|
||||
}
|
||||
|
||||
bool QFSFileEngine::caseSensitive() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString QFSFileEngine::currentPath(const QString &fileName)
|
||||
{
|
||||
QString ret;
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <qdir.h>
|
||||
#include <qelapsedtimer.h>
|
||||
|
||||
#include <private/qfileinfo_p.h>
|
||||
#include <private/qfilesystemengine_p.h>
|
||||
|
||||
#include <utility>
|
||||
@ -57,7 +58,8 @@ public:
|
||||
|
||||
#ifndef QT_NO_FSFILEENGINE
|
||||
bool isCaseSensitive() const {
|
||||
return QFileSystemEngine::isCaseSensitive();
|
||||
auto *fiPriv = QFileInfoPrivate::get(const_cast<QFileInfo*>(&mFileInfo));
|
||||
return QFileSystemEngine::isCaseSensitive(fiPriv->fileEntry, fiPriv->metaData);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -38,6 +38,10 @@
|
||||
#include <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(process)
|
||||
#include <QProcess>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_VXWORKS)
|
||||
#define Q_NO_SYMLINKS
|
||||
#endif
|
||||
@ -242,6 +246,11 @@ private slots:
|
||||
void stdfilesystem();
|
||||
void readSymLink();
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
void fileSystemCaseSensitivity_data();
|
||||
void fileSystemCaseSensitivity();
|
||||
#endif
|
||||
|
||||
private:
|
||||
const QString m_currentDir;
|
||||
QString m_sourceFile;
|
||||
@ -2448,5 +2457,68 @@ void tst_QFileInfo::readSymLink()
|
||||
QFileInfo info(symLinkName);
|
||||
QCOMPARE(info.readSymLink(), QString("../../a"));
|
||||
}
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
void tst_QFileInfo::fileSystemCaseSensitivity_data()
|
||||
{
|
||||
QTest::addColumn<QString>("fileSystemType");
|
||||
QTest::addColumn<QString>("volumeName");
|
||||
QTest::addColumn<Qt::CaseSensitivity>("caseSensitivity");
|
||||
|
||||
QTest::newRow("APFS") << "APFS" << "apfs" << Qt::CaseInsensitive;
|
||||
QTest::newRow("APFS case-sensitive") << "Case-sensitive APFS" << "apfs_case_sensitive" << Qt::CaseSensitive;
|
||||
QTest::newRow("HFS+") << "HFS+" << "hfs" << Qt::CaseInsensitive;
|
||||
QTest::newRow("HFS+ case-sensitive") << "Case-sensitive HFS+" << "hfs_case_sensitive" << Qt::CaseSensitive;
|
||||
QTest::newRow("FAT32") << "MS-DOS FAT32" << "fat32" << Qt::CaseInsensitive;
|
||||
QTest::newRow("ExFAT") << "ExFAT" << "exfat" << Qt::CaseInsensitive;
|
||||
}
|
||||
|
||||
void tst_QFileInfo::fileSystemCaseSensitivity()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("No QProcess available");
|
||||
#else
|
||||
QTemporaryDir tmpDir;
|
||||
QVERIFY2(tmpDir.isValid(), qPrintable(tmpDir.errorString()));
|
||||
|
||||
QFETCH(QString, fileSystemType);
|
||||
QFETCH(QString, volumeName);
|
||||
|
||||
QString imageName = tmpDir.filePath(QString("%2.sparseimage").arg(volumeName));
|
||||
|
||||
QVERIFY(QProcess::execute("hdiutil", { "create", "-quiet",
|
||||
"-type", "SPARSE", "-size", "50m", "-fs", fileSystemType,
|
||||
"-volname", volumeName, imageName }) == 0);
|
||||
|
||||
QVERIFY(QProcess::execute("hdiutil", { "attach", "-quiet",
|
||||
"-mountroot", tmpDir.path(), imageName }) == 0);
|
||||
|
||||
QDir mountPoint(tmpDir.filePath(volumeName));
|
||||
QVERIFY(mountPoint.exists());
|
||||
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
QVERIFY(QProcess::execute("hdiutil", { "detach",
|
||||
"-quiet", mountPoint.absolutePath() }) == 0);
|
||||
});
|
||||
|
||||
QFileInfo lowerCase(mountPoint.filePath("foo"));
|
||||
{
|
||||
QFile file(lowerCase.filePath());
|
||||
QVERIFY(file.open(QFile::WriteOnly));
|
||||
}
|
||||
|
||||
QFileInfo upperCase(mountPoint.filePath("FOO"));
|
||||
{
|
||||
QFile file(upperCase.filePath());
|
||||
QVERIFY(file.open(QFile::WriteOnly));
|
||||
}
|
||||
|
||||
QFETCH(Qt::CaseSensitivity, caseSensitivity);
|
||||
QCOMPARE(lowerCase == upperCase, caseSensitivity == Qt::CaseInsensitive);
|
||||
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
#endif // defined(Q_OS_DARWIN)
|
||||
|
||||
QTEST_MAIN(tst_QFileInfo)
|
||||
#include "tst_qfileinfo.moc"
|
||||
|
@ -24,6 +24,7 @@
|
||||
#if defined(Q_OS_WIN)
|
||||
# include <qt_windows.h> // for SetFileAttributes
|
||||
#endif
|
||||
#include <private/qfileinfo_p.h>
|
||||
#include <private/qfilesystemengine_p.h>
|
||||
|
||||
#include <algorithm>
|
||||
@ -1055,11 +1056,18 @@ void tst_QFileSystemModel::caseSensitivity()
|
||||
indexes.append(index);
|
||||
}
|
||||
|
||||
if (!QFileSystemEngine::isCaseSensitive()) {
|
||||
// QTBUG-31103, QTBUG-64147: Verify that files can be accessed by paths with fLipPeD case.
|
||||
QFileInfo tmpInfo(tmp);
|
||||
auto *tmpInfoPriv = QFileInfoPrivate::get(&tmpInfo);
|
||||
if (!QFileSystemEngine::isCaseSensitive(tmpInfoPriv->fileEntry, tmpInfoPriv->metaData)) {
|
||||
// Verify that files can be accessed by paths with fLipPeD case.
|
||||
for (int i = 0; i < paths.size(); ++i) {
|
||||
const QModelIndex normalCaseIndex = indexes.at(i);
|
||||
const QModelIndex flippedCaseIndex = model->index(flipCase(paths.at(i)));
|
||||
QCOMPARE(indexes.at(i), flippedCaseIndex);
|
||||
#if !defined(Q_OS_WIN)
|
||||
QEXPECT_FAIL("", "QFileSystemModelNodePathKey is hard-coded to be case"
|
||||
" sensitive on non-Windows and case-insensitive on Windows (QTBUG-31103)", Abort);
|
||||
#endif
|
||||
QCOMPARE(normalCaseIndex, flippedCaseIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ private slots:
|
||||
void symLinkTargetPerformanceLNK();
|
||||
void junctionTargetPerformanceMountpoint();
|
||||
#endif
|
||||
void comparison_data();
|
||||
void comparison();
|
||||
};
|
||||
|
||||
void tst_QFileInfo::existsTemporary()
|
||||
@ -72,6 +74,36 @@ void tst_QFileInfo::junctionTargetPerformanceMountpoint()
|
||||
}
|
||||
#endif
|
||||
|
||||
void tst_QFileInfo::comparison_data()
|
||||
{
|
||||
QTest::addColumn<bool>("shouldExist");
|
||||
QTest::addRow("files do not exist") << false;
|
||||
QTest::addRow("files exists") << true;
|
||||
}
|
||||
|
||||
void tst_QFileInfo::comparison()
|
||||
{
|
||||
QTemporaryDir tmpDir;
|
||||
QVERIFY2(tmpDir.isValid(), qPrintable(tmpDir.errorString()));
|
||||
|
||||
QFETCH(bool, shouldExist);
|
||||
|
||||
QFileInfo firstInfo(tmpDir.filePath("first"));
|
||||
QFileInfo secondInfo(tmpDir.filePath("second"));
|
||||
|
||||
if (shouldExist) {
|
||||
QFile first(firstInfo.absoluteFilePath());
|
||||
QVERIFY(first.open(QFile::WriteOnly));
|
||||
QFile second(secondInfo.absoluteFilePath());
|
||||
QVERIFY(second.open(QFile::WriteOnly));
|
||||
}
|
||||
|
||||
QBENCHMARK {
|
||||
// Comparison should be fast because we cache file info
|
||||
[[maybe_unused]] auto r = firstInfo == secondInfo;
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QFileInfo)
|
||||
|
||||
#include "tst_bench_qfileinfo.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user