Skip to content

Commit

Permalink
Darwin: Teach QFileSystemEngine how to resolve case-sensitivity
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
torarnv committed Oct 2, 2024
1 parent 7d6bac5 commit 3d08816
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 28 deletions.
9 changes: 8 additions & 1 deletion src/corelib/io/qdir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
# include "qmutex.h"
#endif

#include <private/qorderedmutexlocker_p.h>

#include <algorithm>
#include <memory>
#include <stdlib.h>
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion src/corelib/io/qfileinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/corelib/io/qfileinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Q_CORE_EXPORT QFileInfo
{
friend class QDirIteratorPrivate;
friend class QDirListingPrivate;
friend class QFileInfoPrivate;
public:
explicit QFileInfo(QFileInfoPrivate *d);

Expand Down
2 changes: 2 additions & 0 deletions src/corelib/io/qfileinfo_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class QFileInfoPrivate : public QSharedData
CachedPerms = 0x100
};

static QFileInfoPrivate *get(QFileInfo *fi) { return fi->d_func(); }

inline QFileInfoPrivate()
: QSharedData(), fileEngine(nullptr),
cachedFlags(0),
Expand Down
10 changes: 3 additions & 7 deletions src/corelib/io/qfilesystemengine_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
24 changes: 23 additions & 1 deletion src/corelib/io/qfilesystemengine_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions src/corelib/io/qfilesystemengine_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/corelib/io/qfilesystemmetadata_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ class Q_AUTOTEST_EXPORT QFileSystemMetaData
UserId = 0x10000000,
GroupId = 0x20000000,

CaseSensitive = 0x80000000,

OwnerIds = UserId | GroupId,

PosixStatFlags = QFileSystemMetaData::OtherPermissions
Expand Down
14 changes: 10 additions & 4 deletions src/corelib/io/qfsfileengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
*/
Expand Down
5 changes: 0 additions & 5 deletions src/corelib/io/qfsfileengine_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 0 additions & 5 deletions src/corelib/io/qfsfileengine_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/gui/itemmodels/qfileinfogatherer_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <qdir.h>
#include <qelapsedtimer.h>

#include <private/qfileinfo_p.h>
#include <private/qfilesystemengine_p.h>

#include <utility>
Expand Down Expand Up @@ -57,7 +58,8 @@ class QExtendedInformation {

#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

Expand Down
72 changes: 72 additions & 0 deletions tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions tests/benchmarks/corelib/io/qfileinfo/tst_bench_qfileinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ private slots:
void symLinkTargetPerformanceLNK();
void junctionTargetPerformanceMountpoint();
#endif
void comparison_data();
void comparison();
};

void tst_QFileInfo::existsTemporary()
Expand Down Expand Up @@ -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"

0 comments on commit 3d08816

Please sign in to comment.