summaryrefslogtreecommitdiffstats
path: root/src/corelib/io
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io')
-rw-r--r--src/corelib/io/forkfd_qt.c (renamed from src/corelib/io/forkfd_qt.cpp)3
-rw-r--r--src/corelib/io/qabstractfileengine.cpp228
-rw-r--r--src/corelib/io/qabstractfileengine_p.h55
-rw-r--r--src/corelib/io/qbuffer.cpp5
-rw-r--r--src/corelib/io/qdataurl.cpp22
-rw-r--r--src/corelib/io/qdataurl_p.h1
-rw-r--r--src/corelib/io/qdebug.cpp169
-rw-r--r--src/corelib/io/qdebug.h70
-rw-r--r--src/corelib/io/qdir.cpp79
-rw-r--r--src/corelib/io/qdir.h6
-rw-r--r--src/corelib/io/qdirentryinfo_p.h156
-rw-r--r--src/corelib/io/qdiriterator.cpp348
-rw-r--r--src/corelib/io/qdirlisting.cpp696
-rw-r--r--src/corelib/io/qdirlisting.h119
-rw-r--r--src/corelib/io/qfile.cpp142
-rw-r--r--src/corelib/io/qfile.h13
-rw-r--r--src/corelib/io/qfiledevice.cpp42
-rw-r--r--src/corelib/io/qfiledevice.h16
-rw-r--r--src/corelib/io/qfileinfo.cpp531
-rw-r--r--src/corelib/io/qfileinfo.h26
-rw-r--r--src/corelib/io/qfileinfo_p.h20
-rw-r--r--src/corelib/io/qfilesystemengine.cpp23
-rw-r--r--src/corelib/io/qfilesystemengine_p.h12
-rw-r--r--src/corelib/io/qfilesystemengine_unix.cpp522
-rw-r--r--src/corelib/io/qfilesystemengine_win.cpp36
-rw-r--r--src/corelib/io/qfilesystementry.cpp24
-rw-r--r--src/corelib/io/qfilesystemiterator_p.h26
-rw-r--r--src/corelib/io/qfilesystemiterator_unix.cpp73
-rw-r--r--src/corelib/io/qfilesystemiterator_win.cpp9
-rw-r--r--src/corelib/io/qfilesystemmetadata_p.h16
-rw-r--r--src/corelib/io/qfilesystemwatcher.cpp53
-rw-r--r--src/corelib/io/qfilesystemwatcher.h4
-rw-r--r--src/corelib/io/qfilesystemwatcher_inotify.cpp3
-rw-r--r--src/corelib/io/qfilesystemwatcher_kqueue.cpp4
-rw-r--r--src/corelib/io/qfilesystemwatcher_p.h12
-rw-r--r--src/corelib/io/qfilesystemwatcher_polling.cpp28
-rw-r--r--src/corelib/io/qfilesystemwatcher_polling_p.h10
-rw-r--r--src/corelib/io/qfilesystemwatcher_win.cpp1
-rw-r--r--src/corelib/io/qfsfileengine.cpp85
-rw-r--r--src/corelib/io/qfsfileengine_iterator.cpp41
-rw-r--r--src/corelib/io/qfsfileengine_iterator_p.h12
-rw-r--r--src/corelib/io/qfsfileengine_p.h24
-rw-r--r--src/corelib/io/qfsfileengine_unix.cpp10
-rw-r--r--src/corelib/io/qfsfileengine_win.cpp35
-rw-r--r--src/corelib/io/qiodevice.cpp14
-rw-r--r--src/corelib/io/qlockfile.cpp6
-rw-r--r--src/corelib/io/qlockfile.h11
-rw-r--r--src/corelib/io/qloggingcategory.h7
-rw-r--r--src/corelib/io/qloggingregistry.cpp53
-rw-r--r--src/corelib/io/qloggingregistry_p.h16
-rw-r--r--src/corelib/io/qnoncontiguousbytedevice.cpp69
-rw-r--r--src/corelib/io/qnoncontiguousbytedevice_p.h3
-rw-r--r--src/corelib/io/qprocess.cpp145
-rw-r--r--src/corelib/io/qprocess.h19
-rw-r--r--src/corelib/io/qprocess_p.h4
-rw-r--r--src/corelib/io/qprocess_unix.cpp623
-rw-r--r--src/corelib/io/qprocess_win.cpp31
-rw-r--r--src/corelib/io/qresource.cpp325
-rw-r--r--src/corelib/io/qresource_iterator.cpp38
-rw-r--r--src/corelib/io/qresource_iterator_p.h7
-rw-r--r--src/corelib/io/qresource_p.h6
-rw-r--r--src/corelib/io/qsavefile.cpp16
-rw-r--r--src/corelib/io/qsavefile.h6
-rw-r--r--src/corelib/io/qsavefile_p.h4
-rw-r--r--src/corelib/io/qsettings.cpp130
-rw-r--r--src/corelib/io/qsettings.h15
-rw-r--r--src/corelib/io/qsettings_p.h3
-rw-r--r--src/corelib/io/qsettings_wasm.cpp453
-rw-r--r--src/corelib/io/qstandardpaths.cpp26
-rw-r--r--src/corelib/io/qstandardpaths.h4
-rw-r--r--src/corelib/io/qstandardpaths_android.cpp5
-rw-r--r--src/corelib/io/qstandardpaths_haiku.cpp4
-rw-r--r--src/corelib/io/qstandardpaths_mac.mm11
-rw-r--r--src/corelib/io/qstandardpaths_unix.cpp19
-rw-r--r--src/corelib/io/qstandardpaths_win.cpp23
-rw-r--r--src/corelib/io/qstorageinfo.cpp26
-rw-r--r--src/corelib/io/qstorageinfo.h15
-rw-r--r--src/corelib/io/qstorageinfo_linux.cpp348
-rw-r--r--src/corelib/io/qstorageinfo_linux_p.h251
-rw-r--r--src/corelib/io/qstorageinfo_mac.cpp9
-rw-r--r--src/corelib/io/qstorageinfo_p.h120
-rw-r--r--src/corelib/io/qstorageinfo_stub.cpp25
-rw-r--r--src/corelib/io/qstorageinfo_unix.cpp468
-rw-r--r--src/corelib/io/qstorageinfo_win.cpp7
-rw-r--r--src/corelib/io/qtemporarydir.cpp6
-rw-r--r--src/corelib/io/qtemporarydir.h4
-rw-r--r--src/corelib/io/qtemporaryfile.cpp51
-rw-r--r--src/corelib/io/qtemporaryfile.h41
-rw-r--r--src/corelib/io/qtemporaryfile_p.h7
-rw-r--r--src/corelib/io/qurl.cpp132
-rw-r--r--src/corelib/io/qurl.h9
-rw-r--r--src/corelib/io/qurlidna.cpp92
-rw-r--r--src/corelib/io/qurlquery.cpp62
-rw-r--r--src/corelib/io/qurlquery.h16
-rw-r--r--src/corelib/io/qwindowspipereader.cpp2
-rw-r--r--src/corelib/io/qwindowspipewriter.cpp2
-rw-r--r--src/corelib/io/qzip.cpp14
97 files changed, 4976 insertions, 2637 deletions
diff --git a/src/corelib/io/forkfd_qt.cpp b/src/corelib/io/forkfd_qt.c
index 3c6d05d6a8..7107b2c6a0 100644
--- a/src/corelib/io/forkfd_qt.cpp
+++ b/src/corelib/io/forkfd_qt.c
@@ -1,6 +1,9 @@
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
#include <QtCore/qglobal.h>
#define FORKFD_NO_SPAWNFD
diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp
index 25dfe79d64..ea5ce13b5e 100644
--- a/src/corelib/io/qabstractfileengine.cpp
+++ b/src/corelib/io/qabstractfileengine.cpp
@@ -10,7 +10,7 @@
#include "qreadwritelock.h"
#include "qvariant.h"
// built-in handlers
-#include "qdiriterator.h"
+#include "qdirlisting.h"
#include "qstringbuilder.h"
#include <QtCore/private/qfilesystementry_p.h>
@@ -19,6 +19,19 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
+static QString appendSlashIfNeeded(const QString &path)
+{
+ if (!path.isEmpty() && !path.endsWith(u'/')
+#ifdef Q_OS_ANDROID
+ && !path.startsWith("content:/"_L1)
+#endif
+ )
+ return QString{path + u'/'};
+ return path;
+}
+
/*!
\class QAbstractFileEngineHandler
\inmodule QtCore
@@ -75,7 +88,10 @@ Q_GLOBAL_STATIC(QReadWriteLock, fileEngineHandlerMutex, QReadWriteLock::Recursiv
Q_CONSTINIT static bool qt_abstractfileenginehandlerlist_shutDown = false;
class QAbstractFileEngineHandlerList : public QList<QAbstractFileEngineHandler *>
{
+ Q_DISABLE_COPY_MOVE(QAbstractFileEngineHandlerList)
public:
+ QAbstractFileEngineHandlerList() = default;
+
~QAbstractFileEngineHandlerList()
{
QWriteLocker locker(fileEngineHandlerMutex());
@@ -121,14 +137,14 @@ QAbstractFileEngineHandler::~QAbstractFileEngineHandler()
Handles calls to custom file engine handlers.
*/
-QAbstractFileEngine *qt_custom_file_engine_handler_create(const QString &path)
+std::unique_ptr<QAbstractFileEngine> qt_custom_file_engine_handler_create(const QString &path)
{
if (qt_file_engine_handlers_in_use.loadRelaxed()) {
QReadLocker locker(fileEngineHandlerMutex());
// check for registered handlers that can load the file
for (QAbstractFileEngineHandler *handler : std::as_const(*fileEngineHandlers())) {
- if (QAbstractFileEngine *engine = handler->create(path))
+ if (auto engine = handler->create(path))
return engine;
}
}
@@ -137,10 +153,11 @@ QAbstractFileEngine *qt_custom_file_engine_handler_create(const QString &path)
}
/*!
- \fn QAbstractFileEngine *QAbstractFileEngineHandler::create(const QString &fileName) const
+ \fn std::unique_ptr<QAbstractFileEngine> QAbstractFileEngineHandler::create(const QString &fileName) const
- Creates a file engine for file \a fileName. Returns 0 if this
- file handler cannot handle \a fileName.
+ If this file handler can handle \a fileName, this method creates a file
+ engine and returns it wrapped in a std::unique_ptr; otherwise returns
+ nullptr.
Example:
@@ -162,16 +179,15 @@ QAbstractFileEngine *qt_custom_file_engine_handler_create(const QString &path)
\sa QAbstractFileEngineHandler
*/
-QAbstractFileEngine *QAbstractFileEngine::create(const QString &fileName)
+std::unique_ptr<QAbstractFileEngine> QAbstractFileEngine::create(const QString &fileName)
{
QFileSystemEntry entry(fileName);
QFileSystemMetaData metaData;
- QAbstractFileEngine *engine = QFileSystemEngine::resolveEntryAndCreateLegacyEngine(entry, metaData);
+ auto engine = QFileSystemEngine::createLegacyEngine(entry, metaData);
#ifndef QT_NO_FSFILEENGINE
- if (!engine)
- // fall back to regular file engine
- return new QFSFileEngine(entry.filePath());
+ if (!engine) // fall back to regular file engine
+ engine = std::make_unique<QFSFileEngine>(entry.filePath());
#endif
return engine;
@@ -289,20 +305,6 @@ QAbstractFileEngine *QAbstractFileEngine::create(const QString &fileName)
*/
/*!
- \enum QAbstractFileEngine::FileTime
-
- These are used by the fileTime() function.
-
- \value BirthTime When the file was born (created).
- \value MetadataChangeTime When the file's metadata was last changed.
- \value ModificationTime When the file was most recently modified.
- \value AccessTime When the file was most recently accessed (e.g.
- read or written to).
-
- \sa setFileName()
-*/
-
-/*!
\enum QAbstractFileEngine::FileOwner
\value OwnerUser The user who owns the file.
@@ -590,12 +592,15 @@ bool QAbstractFileEngine::isRelativePath() const
QStringList QAbstractFileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const
{
QStringList ret;
- QDirIterator it(fileName(), filterNames, filters);
- while (it.hasNext()) {
- it.next();
- ret << it.fileName();
- }
+#ifdef QT_BOOTSTRAPPED
+ Q_UNUSED(filters);
+ Q_UNUSED(filterNames);
+ Q_UNREACHABLE_RETURN(ret);
+#else
+ for (const auto &dirEntry : QDirListing(fileName(), filterNames, filters))
+ ret.emplace_back(dirEntry.fileName());
return ret;
+#endif
}
/*!
@@ -695,7 +700,7 @@ QString QAbstractFileEngine::owner(FileOwner owner) const
\sa fileTime()
*/
-bool QAbstractFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
+bool QAbstractFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
{
Q_UNUSED(newDate);
Q_UNUSED(time);
@@ -712,7 +717,7 @@ bool QAbstractFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
\sa setFileName(), QDateTime, QDateTime::isValid(), FileTime
*/
-QDateTime QAbstractFileEngine::fileTime(FileTime time) const
+QDateTime QAbstractFileEngine::fileTime(QFile::FileTime time) const
{
Q_UNUSED(time);
return QDateTime();
@@ -774,10 +779,7 @@ bool QAbstractFileEngine::atEnd() const
uchar *QAbstractFileEngine::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
{
- MapExtensionOption option;
- option.offset = offset;
- option.size = size;
- option.flags = flags;
+ const MapExtensionOption option(offset, size, flags);
MapExtensionReturn r;
if (!extension(MapExtension, &option, &r))
return nullptr;
@@ -798,8 +800,7 @@ uchar *QAbstractFileEngine::map(qint64 offset, qint64 size, QFile::MemoryMapFlag
*/
bool QAbstractFileEngine::unmap(uchar *address)
{
- UnMapExtensionOption options;
- options.address = address;
+ const UnMapExtensionOption options(address);
return extension(UnMapExtension, &options);
}
@@ -826,11 +827,12 @@ bool QAbstractFileEngine::cloneTo(QAbstractFileEngine *target)
\internal
If all you want is to iterate over entries in a directory, see
- QDirIterator instead. This class is only for custom file engine authors.
+ QDirListing instead. This class is useful only for custom file engine
+ authors.
QAbstractFileEngineIterator is a unidirectional single-use virtual
- iterator that plugs into QDirIterator, providing transparent proxy
- iteration for custom file engines.
+ iterator that plugs into QDirListing, providing transparent proxy
+ iteration for custom file engines (for example, QResourceFileEngine).
You can subclass QAbstractFileEngineIterator to provide an iterator when
writing your own file engine. To plug the iterator into your file system,
@@ -851,10 +853,11 @@ bool QAbstractFileEngine::cloneTo(QAbstractFileEngine *target)
You can call dirName() to get the directory name, nameFilters() to get a
stringlist of name filters, and filters() to get the entry filters.
- The pure virtual function hasNext() returns \c true if the current directory
- has at least one more entry (i.e., the directory name is valid and
- accessible, and we have not reached the end of the entry list), and false
- otherwise. Reimplement next() to seek to the next entry.
+ The pure virtual function advance(), as its name implies, advances the
+ iterator to the next entry in the current directory; if the operation
+ was successful this method returns \c true, otherwise it returns \c
+ false. You have to reimplement this function in your sub-class to work
+ with your file engine implementation.
The pure virtual function currentFileName() returns the name of the
current entry without advancing the iterator. The currentFilePath()
@@ -869,15 +872,7 @@ bool QAbstractFileEngine::cloneTo(QAbstractFileEngine *target)
Note: QAbstractFileEngineIterator does not deal with QDir::IteratorFlags;
it simply returns entries for a single directory.
- \sa QDirIterator
-*/
-
-/*!
- \enum QAbstractFileEngineIterator::EntryInfoType
- \internal
-
- This enum describes the different types of information that can be
- requested through the QAbstractFileEngineIterator::entryInfo() function.
+ \sa QDirListing
*/
/*!
@@ -887,56 +882,45 @@ bool QAbstractFileEngine::cloneTo(QAbstractFileEngine *target)
Synonym for QAbstractFileEngineIterator.
*/
-class QAbstractFileEngineIteratorPrivate
-{
-public:
- QString path;
- QDir::Filters filters;
- QStringList nameFilters;
- QFileInfo fileInfo;
-};
+/*!
+ \typedef QAbstractFileEngine::IteratorUniquePtr
+ \since 6.8
+
+ Synonym for std::unique_ptr<Iterator> (that is a
+ std::unique_ptr<QAbstractFileEngineIterator>).
+*/
/*!
Constructs a QAbstractFileEngineIterator, using the entry filters \a
filters, and wildcard name filters \a nameFilters.
*/
-QAbstractFileEngineIterator::QAbstractFileEngineIterator(QDir::Filters filters,
+QAbstractFileEngineIterator::QAbstractFileEngineIterator(const QString &path, QDir::Filters filters,
const QStringList &nameFilters)
- : d(new QAbstractFileEngineIteratorPrivate)
+ : m_filters(filters),
+ m_nameFilters(nameFilters),
+ m_path(appendSlashIfNeeded(path))
{
- d->nameFilters = nameFilters;
- d->filters = filters;
}
/*!
Destroys the QAbstractFileEngineIterator.
- \sa QDirIterator
+ \sa QDirListing
*/
QAbstractFileEngineIterator::~QAbstractFileEngineIterator()
{
}
/*!
- Returns the path for this iterator. QDirIterator is responsible for
- assigning this path; it cannot change during the iterator's lifetime.
+
+ Returns the path for this iterator. The path is set by beginEntryList().
+ The path should't be changed once iteration begins.
\sa nameFilters(), filters()
*/
QString QAbstractFileEngineIterator::path() const
{
- return d->path;
-}
-
-/*!
- \internal
-
- Sets the iterator path to \a path. This function is called from within
- QDirIterator.
-*/
-void QAbstractFileEngineIterator::setPath(const QString &path)
-{
- d->path = path;
+ return m_path;
}
/*!
@@ -946,7 +930,7 @@ void QAbstractFileEngineIterator::setPath(const QString &path)
*/
QStringList QAbstractFileEngineIterator::nameFilters() const
{
- return d->nameFilters;
+ return m_nameFilters;
}
/*!
@@ -956,7 +940,7 @@ QStringList QAbstractFileEngineIterator::nameFilters() const
*/
QDir::Filters QAbstractFileEngineIterator::filters() const
{
- return d->filters;
+ return m_filters;
}
/*!
@@ -977,15 +961,10 @@ QDir::Filters QAbstractFileEngineIterator::filters() const
QString QAbstractFileEngineIterator::currentFilePath() const
{
QString name = currentFileName();
- if (!name.isNull()) {
- QString tmp = path();
- if (!tmp.isEmpty()) {
- if (!tmp.endsWith(u'/'))
- tmp.append(u'/');
- name.prepend(tmp);
- }
- }
- return name;
+ if (name.isNull())
+ return name;
+
+ return path() + name;
}
/*!
@@ -1000,75 +979,42 @@ QString QAbstractFileEngineIterator::currentFilePath() const
QFileInfo QAbstractFileEngineIterator::currentFileInfo() const
{
QString path = currentFilePath();
- if (d->fileInfo.filePath() != path)
- d->fileInfo.setFile(path);
+ if (m_fileInfo.filePath() != path)
+ m_fileInfo.setFile(path);
// return a shallow copy
- return d->fileInfo;
-}
-
-/*!
- \internal
-
- Returns the entry info \a type for this iterator's current directory entry
- as a QVariant. If \a type is undefined for this entry, a null QVariant is
- returned.
-
- \sa QAbstractFileEngine::beginEntryList(), QDir::beginEntryList()
-*/
-QVariant QAbstractFileEngineIterator::entryInfo(EntryInfoType type) const
-{
- Q_UNUSED(type);
- return QVariant();
+ return m_fileInfo;
}
/*!
- \fn virtual QString QAbstractFileEngineIterator::next() = 0
+ \fn virtual bool QAbstractFileEngineIterator::advance() = 0
This pure virtual function advances the iterator to the next directory
- entry, and returns the file path to the current entry.
+ entry; if the operation was successful this method returns \c true,
+ otherwise it returs \c false.
This function can optionally make use of nameFilters() and filters() to
optimize its performance.
Reimplement this function in a subclass to advance the iterator.
-
- \sa QDirIterator::next()
-*/
-
-/*!
- \fn virtual bool QAbstractFileEngineIterator::hasNext() const = 0
-
- This pure virtual function returns \c true if there is at least one more
- entry in the current directory (i.e., the iterator path is valid and
- accessible, and the iterator has not reached the end of the entry list).
-
- \sa QDirIterator::hasNext()
*/
/*!
- Returns an instance of a QAbstractFileEngineIterator using \a filters for
- entry filtering and \a filterNames for name filtering. This function is
- called by QDirIterator to initiate directory iteration.
+ Returns a QAbstractFileEngine::IteratorUniquePtr, that can be used
+ to iterate over the entries in \a path, using \a filters for entry
+ filtering and \a filterNames for name filtering. This function is called
+ by QDirListing to initiate directory iteration.
- QDirIterator takes ownership of the returned instance, and deletes it when
- it's done.
-
- \sa QDirIterator
+ \sa QDirListing
*/
-QAbstractFileEngine::Iterator *QAbstractFileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames)
+QAbstractFileEngine::IteratorUniquePtr
+QAbstractFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
{
+ Q_UNUSED(path);
Q_UNUSED(filters);
Q_UNUSED(filterNames);
- return nullptr;
-}
-
-/*!
- \internal
-*/
-QAbstractFileEngine::Iterator *QAbstractFileEngine::endEntryList()
-{
- return nullptr;
+ return {};
}
/*!
diff --git a/src/corelib/io/qabstractfileengine_p.h b/src/corelib/io/qabstractfileengine_p.h
index 28f07bf8f9..b2e0b248da 100644
--- a/src/corelib/io/qabstractfileengine_p.h
+++ b/src/corelib/io/qabstractfileengine_p.h
@@ -19,6 +19,7 @@
#include "QtCore/qfile.h"
#include "QtCore/qdir.h"
+#include <memory>
#include <optional>
#ifdef open
@@ -80,12 +81,7 @@ public:
OwnerUser,
OwnerGroup
};
- enum FileTime {
- AccessTime,
- BirthTime,
- MetadataChangeTime,
- ModificationTime
- };
+
virtual ~QAbstractFileEngine();
@@ -116,8 +112,8 @@ public:
virtual QString fileName(FileName file=DefaultName) const;
virtual uint ownerId(FileOwner) const;
virtual QString owner(FileOwner) const;
- virtual bool setFileTime(const QDateTime &newDate, FileTime time);
- virtual QDateTime fileTime(FileTime time) const;
+ virtual bool setFileTime(const QDateTime &newDate, QFile::FileTime time);
+ virtual QDateTime fileTime(QFile::FileTime time) const;
virtual void setFileName(const QString &file);
virtual int handle() const;
virtual bool cloneTo(QAbstractFileEngine *target);
@@ -126,8 +122,11 @@ public:
bool unmap(uchar *ptr);
typedef QAbstractFileEngineIterator Iterator;
- virtual Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames);
- virtual Iterator *endEntryList();
+ using IteratorUniquePtr = std::unique_ptr<Iterator>;
+
+ virtual IteratorUniquePtr
+ beginEntryList(const QString &path, QDir::Filters filters, const QStringList &filterNames);
+ virtual IteratorUniquePtr endEntryList() { return {}; }
virtual qint64 read(char *data, qint64 maxlen);
virtual qint64 readLine(char *data, qint64 maxlen);
@@ -148,26 +147,33 @@ public:
{};
class MapExtensionOption : public ExtensionOption {
+ Q_DISABLE_COPY_MOVE(MapExtensionOption)
public:
qint64 offset;
qint64 size;
QFile::MemoryMapFlags flags;
+ constexpr MapExtensionOption(qint64 off, qint64 sz, QFile::MemoryMapFlags f)
+ : offset(off), size(sz), flags(f) {}
};
class MapExtensionReturn : public ExtensionReturn {
+ Q_DISABLE_COPY_MOVE(MapExtensionReturn)
public:
- uchar *address;
+ MapExtensionReturn() = default;
+ uchar *address = nullptr;
};
class UnMapExtensionOption : public ExtensionOption {
+ Q_DISABLE_COPY_MOVE(UnMapExtensionOption)
public:
- uchar *address;
+ uchar *address = nullptr;
+ constexpr UnMapExtensionOption(uchar *p) : address(p) {}
};
virtual bool extension(Extension extension, const ExtensionOption *option = nullptr, ExtensionReturn *output = nullptr);
virtual bool supportsExtension(Extension extension) const;
// Factory
- static QAbstractFileEngine *create(const QString &fileName);
+ static std::unique_ptr<QAbstractFileEngine> create(const QString &fileName);
protected:
void setError(QFile::FileError error, const QString &str);
@@ -185,21 +191,21 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFileEngine::FileFlags)
class Q_CORE_EXPORT QAbstractFileEngineHandler
{
+ Q_DISABLE_COPY_MOVE(QAbstractFileEngineHandler)
public:
QAbstractFileEngineHandler();
virtual ~QAbstractFileEngineHandler();
- virtual QAbstractFileEngine *create(const QString &fileName) const = 0;
+ virtual std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const = 0;
};
-class QAbstractFileEngineIteratorPrivate;
class Q_CORE_EXPORT QAbstractFileEngineIterator
{
public:
- QAbstractFileEngineIterator(QDir::Filters filters, const QStringList &nameFilters);
+ QAbstractFileEngineIterator(const QString &path, QDir::Filters filters,
+ const QStringList &nameFilters);
virtual ~QAbstractFileEngineIterator();
- virtual QString next() = 0;
- virtual bool hasNext() const = 0;
+ virtual bool advance() = 0;
QString path() const;
QStringList nameFilters() const;
@@ -210,16 +216,17 @@ public:
virtual QString currentFilePath() const;
protected:
- enum EntryInfoType {
- };
- virtual QVariant entryInfo(EntryInfoType type) const;
+ mutable QFileInfo m_fileInfo;
private:
Q_DISABLE_COPY_MOVE(QAbstractFileEngineIterator)
friend class QDirIterator;
friend class QDirIteratorPrivate;
- void setPath(const QString &path);
- QScopedPointer<QAbstractFileEngineIteratorPrivate> d;
+ friend class QDirListingPrivate;
+
+ QDir::Filters m_filters;
+ QStringList m_nameFilters;
+ QString m_path;
};
class QAbstractFileEnginePrivate
@@ -238,7 +245,7 @@ public:
Q_DECLARE_PUBLIC(QAbstractFileEngine)
};
-QAbstractFileEngine *qt_custom_file_engine_handler_create(const QString &path);
+std::unique_ptr<QAbstractFileEngine> qt_custom_file_engine_handler_create(const QString &path);
QT_END_NAMESPACE
diff --git a/src/corelib/io/qbuffer.cpp b/src/corelib/io/qbuffer.cpp
index ba1ce7463b..4e513bc7cf 100644
--- a/src/corelib/io/qbuffer.cpp
+++ b/src/corelib/io/qbuffer.cpp
@@ -282,8 +282,7 @@ void QBuffer::setData(const char *data, qsizetype size)
qWarning("QBuffer::setData: Buffer is open");
return;
}
- d->buf->replace(qsizetype(0), d->buf->size(), // ### QByteArray lacks assign(ptr, n)
- data, size);
+ d->buf->assign(data, data + size);
}
/*!
@@ -401,7 +400,7 @@ qint64 QBuffer::writeData(const char *data, qint64 len)
if (required > quint64(d->buf->size())) { // capacity exceeded
// The following must hold, since qsizetype covers half the virtual address space:
- Q_ASSUME(required <= quint64((std::numeric_limits<qsizetype>::max)()));
+ Q_ASSERT(required <= quint64((std::numeric_limits<qsizetype>::max)()));
d->buf->resize(qsizetype(required));
if (quint64(d->buf->size()) != required) { // could not resize
qWarning("QBuffer::writeData: Memory allocation error");
diff --git a/src/corelib/io/qdataurl.cpp b/src/corelib/io/qdataurl.cpp
index 92c6f54122..65b934b3f6 100644
--- a/src/corelib/io/qdataurl.cpp
+++ b/src/corelib/io/qdataurl.cpp
@@ -26,32 +26,36 @@ Q_CORE_EXPORT bool qDecodeDataUrl(const QUrl &uri, QString &mimeType, QByteArray
// reality often differs from the specification. People have
// data: URIs with ? and #
//QByteArray data = QByteArray::fromPercentEncoding(uri.path(QUrl::FullyEncoded).toLatin1());
- QByteArray data = QByteArray::fromPercentEncoding(uri.url(QUrl::FullyEncoded | QUrl::RemoveScheme).toLatin1());
+ const QByteArray dataArray =
+ QByteArray::fromPercentEncoding(uri.url(QUrl::FullyEncoded | QUrl::RemoveScheme).toLatin1());
+ QByteArrayView data = dataArray;
// parse it:
const qsizetype pos = data.indexOf(',');
if (pos != -1) {
- payload = data.mid(pos + 1);
+ payload = data.mid(pos + 1).toByteArray();
data.truncate(pos);
data = data.trimmed();
// find out if the payload is encoded in Base64
- if (QLatin1StringView{data}.endsWith(";base64"_L1, Qt::CaseInsensitive)) {
+ constexpr auto base64 = ";base64"_L1;
+ if (QLatin1StringView{data}.endsWith(base64, Qt::CaseInsensitive)) {
payload = QByteArray::fromBase64(payload);
- data.chop(7);
+ data.chop(base64.size());
}
- if (QLatin1StringView{data}.startsWith("charset"_L1, Qt::CaseInsensitive)) {
- qsizetype i = 7; // strlen("charset")
+ QLatin1StringView textPlain;
+ constexpr auto charset = "charset"_L1;
+ if (QLatin1StringView{data}.startsWith(charset, Qt::CaseInsensitive)) {
+ qsizetype i = charset.size();
while (data.at(i) == ' ')
++i;
if (data.at(i) == '=')
- data.prepend("text/plain;");
+ textPlain = "text/plain;"_L1;
}
if (!data.isEmpty())
- mimeType = QString::fromLatin1(data.trimmed());
-
+ mimeType = textPlain + QLatin1StringView(data.trimmed());
}
return true;
diff --git a/src/corelib/io/qdataurl_p.h b/src/corelib/io/qdataurl_p.h
index 91b8333108..2763056cc4 100644
--- a/src/corelib/io/qdataurl_p.h
+++ b/src/corelib/io/qdataurl_p.h
@@ -19,7 +19,6 @@
#include "QtCore/qurl.h"
#include "QtCore/qbytearray.h"
#include "QtCore/qstring.h"
-#include "QtCore/qpair.h"
QT_BEGIN_NAMESPACE
diff --git a/src/corelib/io/qdebug.cpp b/src/corelib/io/qdebug.cpp
index 56deeb7cf7..64b693fea5 100644
--- a/src/corelib/io/qdebug.cpp
+++ b/src/corelib/io/qdebug.cpp
@@ -2,19 +2,14 @@
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifdef QT_NO_DEBUG
-#undef QT_NO_DEBUG
-#endif
-#ifdef qDebug
-#undef qDebug
-#endif
-
#include "qdebug.h"
#include "private/qdebug_p.h"
#include "qmetaobject.h"
+#include <private/qlogging_p.h>
#include <private/qtextstream_p.h>
#include <private/qtools_p.h>
+#include <array>
#include <q20chrono.h>
QT_BEGIN_NAMESPACE
@@ -156,15 +151,15 @@ QByteArray QtDebugUtils::toPrintable(const char *data, qint64 len, qsizetype max
Flushes any pending data to be written and destroys the debug stream.
*/
-// Has been defined in the header / inlined before Qt 5.4
QDebug::~QDebug()
{
if (stream && !--stream->ref) {
if (stream->space && stream->buffer.endsWith(u' '))
stream->buffer.chop(1);
if (stream->message_output) {
+ QInternalMessageLogContext ctxt(stream->context);
qt_message_output(stream->type,
- stream->context,
+ ctxt,
stream->buffer);
}
delete stream;
@@ -347,12 +342,7 @@ void QDebug::putByteArray(const char *begin, size_t length, Latin1Content conten
}
}
-/*!
- \since 6.6
- \internal
- Helper to the std::chrono::duration debug streaming output.
- */
-QByteArray QDebug::timeUnit(qint64 num, qint64 den)
+static QByteArray timeUnit(qint64 num, qint64 den)
{
using namespace std::chrono;
using namespace q20::chrono;
@@ -432,6 +422,97 @@ QByteArray QDebug::timeUnit(qint64 num, qint64 den)
}
/*!
+ \since 6.6
+ \internal
+ Helper to the std::chrono::duration debug streaming output.
+ */
+void QDebug::putTimeUnit(qint64 num, qint64 den)
+{
+ stream->ts << timeUnit(num, den); // ### optimize
+}
+
+namespace {
+
+#ifdef QT_SUPPORTS_INT128
+
+constexpr char Q_INT128_MIN_STR[] = "-170141183460469231731687303715884105728";
+
+constexpr int Int128BufferSize = sizeof(Q_INT128_MIN_STR);
+using Int128Buffer = std::array<char, Int128BufferSize>;
+ // numeric_limits<qint128>::digits10 may not exist
+
+static char *i128ToStringHelper(Int128Buffer &buffer, quint128 n)
+{
+ auto dst = buffer.data() + buffer.size();
+ *--dst = '\0'; // NUL-terminate
+ if (n == 0) {
+ *--dst = '0'; // and done
+ } else {
+ while (n != 0) {
+ *--dst = "0123456789"[n % 10];
+ n /= 10;
+ }
+ }
+ return dst;
+}
+#endif // QT_SUPPORTS_INT128
+
+[[maybe_unused]]
+static const char *int128Warning()
+{
+ const char *msg = "Qt was not compiled with int128 support.";
+ qWarning("%s", msg);
+ return msg;
+}
+
+} // unnamed namespace
+
+/*!
+ \since 6.7
+ \internal
+ Helper to the qint128 debug streaming output.
+ */
+void QDebug::putInt128([[maybe_unused]] const void *p)
+{
+#ifdef QT_SUPPORTS_INT128
+ Q_ASSERT(p);
+ qint128 i;
+ memcpy(&i, p, sizeof(i)); // alignment paranoia
+ if (i == Q_INT128_MIN) {
+ // -i is not representable, hardcode the result:
+ stream->ts << Q_INT128_MIN_STR;
+ } else {
+ Int128Buffer buffer;
+ auto dst = i128ToStringHelper(buffer, i < 0 ? -i : i);
+ if (i < 0)
+ *--dst = '-';
+ stream->ts << dst;
+ }
+ return;
+#endif // QT_SUPPORTS_INT128
+ stream->ts << int128Warning();
+}
+
+/*!
+ \since 6.7
+ \internal
+ Helper to the quint128 debug streaming output.
+ */
+void QDebug::putUInt128([[maybe_unused]] const void *p)
+{
+#ifdef QT_SUPPORTS_INT128
+ Q_ASSERT(p);
+ quint128 i;
+ memcpy(&i, p, sizeof(i)); // alignment paranoia
+ Int128Buffer buffer;
+ stream->ts << i128ToStringHelper(buffer, i);
+ return;
+#endif // QT_SUPPORTS_INT128
+ stream->ts << int128Warning();
+}
+
+
+/*!
\fn QDebug::swap(QDebug &other)
\since 5.0
@@ -507,6 +588,29 @@ QDebug &QDebug::resetFormat()
/*!
+ \fn bool QDebug::quoteStrings() const
+ \since 6.7
+
+ Returns \c true if this QDebug instance will quote strings streamed into
+ it (which is the default).
+
+ \sa QDebugStateSaver, quote(), noquote(), setQuoteStrings()
+*/
+
+/*!
+ \fn void QDebug::setQuoteStrings(bool b)
+ \since 6.7
+
+ Enables quoting of strings streamed into this QDebug instance if \a b is
+ \c true; otherwise quoting is disabled.
+
+ The default is to quote strings.
+
+ \sa QDebugStateSaver, quote(), noquote(), quoteStrings()
+*/
+
+
+/*!
\fn QDebug &QDebug::quote()
\since 5.4
@@ -875,6 +979,23 @@ QDebug &QDebug::resetFormat()
*/
/*!
+ \fn template <typename T, QDebug::if_qint128<T>> QDebug::operator<<(T i)
+ \fn template <typename T, QDebug::if_quint128<T>> QDebug::operator<<(T i)
+ \since 6.7
+
+ Prints the textual representation of the 128-bit integer \a i.
+
+ \note This operator is only available if Qt supports 128-bit integer types.
+ If 128-bit integer types are available in your build, but the Qt libraries
+ were compiled without, the operator will print a warning instead.
+
+ \note Because the operator is a function template, no implicit conversions
+ are performed on its argument. It must be exactly qint128/quint128.
+
+ \sa QT_SUPPORTS_INT128
+*/
+
+/*!
\fn template <class T> QString QDebug::toString(T &&object)
\since 6.0
@@ -975,7 +1096,7 @@ QDebug &QDebug::resetFormat()
*/
/*!
- \fn template <class T1, class T2> QDebug operator<<(QDebug debug, const QPair<T1, T2> &pair)
+ \fn template <class T1, class T2> QDebug operator<<(QDebug debug, const std::pair<T1, T2> &pair)
\relates QDebug
Writes the contents of \a pair to \a debug. Both \c T1 and
@@ -983,11 +1104,12 @@ QDebug &QDebug::resetFormat()
*/
/*!
- \fn template <class T1, class T2> QDebug operator<<(QDebug debug, const std::pair<T1, T2> &pair)
+ \since 6.7
+ \fn template <class T> QDebug operator<<(QDebug debug, const std::optional<T> &opt)
\relates QDebug
- Writes the contents of \a pair to \a debug. Both \c T1 and
- \c T2 need to support streaming into QDebug.
+ Writes the contents of \a opt (or \c nullopt if not set) to \a debug.
+ \c T needs to support streaming into QDebug.
*/
/*!
@@ -1023,6 +1145,13 @@ QDebug &QDebug::resetFormat()
*/
/*!
+ \since 6.7
+ \fn QDebug &QDebug::operator<<(std::nullopt_t)
+
+ Writes nullopt to the stream.
+*/
+
+/*!
\class QDebugStateSaver
\inmodule QtCore
\brief Convenience class for custom QDebug operators.
@@ -1040,7 +1169,7 @@ QDebug &QDebug::resetFormat()
QDebugStateSaver is typically used in the implementation of an operator<<() for debugging:
- \snippet tools/customtype/message.cpp custom type streaming operator
+ \snippet customtype/customtypeexample.cpp custom type streaming operator
\since 5.1
*/
diff --git a/src/corelib/io/qdebug.h b/src/corelib/io/qdebug.h
index 3fa9c42052..4797bcd169 100644
--- a/src/corelib/io/qdebug.h
+++ b/src/corelib/io/qdebug.h
@@ -11,6 +11,7 @@
#include <QtCore/qcontainerfwd.h>
#include <QtCore/qtextstream.h>
+#include <QtCore/qtypes.h>
#include <QtCore/qstring.h>
#include <QtCore/qcontiguouscache.h>
#include <QtCore/qsharedpointer.h>
@@ -19,6 +20,7 @@
#include <chrono>
#include <list>
#include <map>
+#include <optional>
#include <string>
#include <string_view>
#include <utility>
@@ -68,7 +70,9 @@ class QT6_ONLY(Q_CORE_EXPORT) QDebug : public QIODeviceBase
QT7_ONLY(Q_CORE_EXPORT) void putUcs4(uint ucs4);
QT7_ONLY(Q_CORE_EXPORT) void putString(const QChar *begin, size_t length);
QT7_ONLY(Q_CORE_EXPORT) void putByteArray(const char *begin, size_t length, Latin1Content content);
- QT7_ONLY(Q_CORE_EXPORT) static QByteArray timeUnit(qint64 num, qint64 den);
+ QT7_ONLY(Q_CORE_EXPORT) void putTimeUnit(qint64 num, qint64 den);
+ QT7_ONLY(Q_CORE_EXPORT) void putInt128(const void *i);
+ QT7_ONLY(Q_CORE_EXPORT) void putUInt128(const void *i);
public:
explicit QDebug(QIODevice *device) : stream(new Stream(device)) {}
explicit QDebug(QString *string) : stream(new Stream(string)) {}
@@ -93,6 +97,9 @@ public:
bool autoInsertSpaces() const { return stream->space; }
void setAutoInsertSpaces(bool b) { stream->space = b; }
+ [[nodiscard]] bool quoteStrings() const noexcept { return !stream->noQuotes; }
+ void setQuoteStrings(bool b) { stream->noQuotes = !b; }
+
inline QDebug &quote() { stream->noQuotes = false; return *this; }
inline QDebug &noquote() { stream->noQuotes = true; return *this; }
inline QDebug &maybeQuote(char c = '"') { if (!stream->noQuotes) stream->ts << c; return *this; }
@@ -123,6 +130,7 @@ public:
inline QDebug &operator<<(QByteArrayView t) { putByteArray(t.constData(), t.size(), ContainsBinary); return maybeSpace(); }
inline QDebug &operator<<(const void * t) { stream->ts << t; return maybeSpace(); }
inline QDebug &operator<<(std::nullptr_t) { stream->ts << "(nullptr)"; return maybeSpace(); }
+ inline QDebug &operator<<(std::nullopt_t) { stream->ts << "nullopt"; return maybeSpace(); }
inline QDebug &operator<<(QTextStreamFunction f) {
stream->ts << f;
return *this;
@@ -194,10 +202,26 @@ public:
template <typename Rep, typename Period>
QDebug &operator<<(std::chrono::duration<Rep, Period> duration)
{
- stream->ts << duration.count() << timeUnit(Period::num, Period::den);
+ stream->ts << duration.count();
+ putTimeUnit(Period::num, Period::den);
return maybeSpace();
}
+#ifdef QT_SUPPORTS_INT128
+private:
+ // Constrained templates so they only match q(u)int128 without conversions.
+ // Also keeps these operators out of the ABI.
+ template <typename T>
+ using if_qint128 = std::enable_if_t<std::is_same_v<T, qint128>, bool>;
+ template <typename T>
+ using if_quint128 = std::enable_if_t<std::is_same_v<T, quint128>, bool>;
+public:
+ template <typename T, if_qint128<T> = true>
+ QDebug &operator<<(T i128) { putInt128(&i128); return maybeSpace(); }
+ template <typename T, if_quint128<T> = true>
+ QDebug &operator<<(T u128) { putUInt128(&u128); return maybeSpace(); }
+#endif // QT_SUPPORTS_INT128
+
template <typename T>
static QString toString(T &&object)
{
@@ -211,10 +235,12 @@ public:
Q_DECLARE_SHARED(QDebug)
class QDebugStateSaverPrivate;
-class Q_CORE_EXPORT QDebugStateSaver
+class QDebugStateSaver
{
public:
+ Q_NODISCARD_CTOR Q_CORE_EXPORT
QDebugStateSaver(QDebug &dbg);
+ Q_CORE_EXPORT
~QDebugStateSaver();
private:
Q_DISABLE_COPY(QDebugStateSaver)
@@ -292,67 +318,78 @@ using QDebugIfHasDebugStreamContainer =
template<typename T>
inline QDebugIfHasDebugStreamContainer<QList<T>, T> operator<<(QDebug debug, const QList<T> &vec)
{
- return QtPrivate::printSequentialContainer(debug, "QList", vec);
+ return QtPrivate::printSequentialContainer(std::move(debug), "QList", vec);
}
template<typename T, qsizetype P>
inline QDebugIfHasDebugStream<T> operator<<(QDebug debug, const QVarLengthArray<T, P> &vec)
{
- return QtPrivate::printSequentialContainer(debug, "QVarLengthArray", vec);
+ return QtPrivate::printSequentialContainer(std::move(debug), "QVarLengthArray", vec);
}
template <typename T, typename Alloc>
inline QDebugIfHasDebugStream<T> operator<<(QDebug debug, const std::vector<T, Alloc> &vec)
{
- return QtPrivate::printSequentialContainer(debug, "std::vector", vec);
+ return QtPrivate::printSequentialContainer(std::move(debug), "std::vector", vec);
}
template <typename T, typename Alloc>
inline QDebugIfHasDebugStream<T> operator<<(QDebug debug, const std::list<T, Alloc> &vec)
{
- return QtPrivate::printSequentialContainer(debug, "std::list", vec);
+ return QtPrivate::printSequentialContainer(std::move(debug), "std::list", vec);
}
template <typename T>
inline QDebugIfHasDebugStream<T> operator<<(QDebug debug, std::initializer_list<T> list)
{
- return QtPrivate::printSequentialContainer(debug, "std::initializer_list", list);
+ return QtPrivate::printSequentialContainer(std::move(debug), "std::initializer_list", list);
}
template <typename Key, typename T, typename Compare, typename Alloc>
inline QDebugIfHasDebugStream<Key, T> operator<<(QDebug debug, const std::map<Key, T, Compare, Alloc> &map)
{
- return QtPrivate::printSequentialContainer(debug, "std::map", map); // yes, sequential: *it is std::pair
+ return QtPrivate::printSequentialContainer(std::move(debug), "std::map", map); // yes, sequential: *it is std::pair
}
template <typename Key, typename T, typename Compare, typename Alloc>
inline QDebugIfHasDebugStream<Key, T> operator<<(QDebug debug, const std::multimap<Key, T, Compare, Alloc> &map)
{
- return QtPrivate::printSequentialContainer(debug, "std::multimap", map); // yes, sequential: *it is std::pair
+ return QtPrivate::printSequentialContainer(std::move(debug), "std::multimap", map); // yes, sequential: *it is std::pair
}
template <class Key, class T>
inline QDebugIfHasDebugStreamContainer<QMap<Key, T>, Key, T> operator<<(QDebug debug, const QMap<Key, T> &map)
{
- return QtPrivate::printAssociativeContainer(debug, "QMap", map);
+ return QtPrivate::printAssociativeContainer(std::move(debug), "QMap", map);
}
template <class Key, class T>
inline QDebugIfHasDebugStreamContainer<QMultiMap<Key, T>, Key, T> operator<<(QDebug debug, const QMultiMap<Key, T> &map)
{
- return QtPrivate::printAssociativeContainer(debug, "QMultiMap", map);
+ return QtPrivate::printAssociativeContainer(std::move(debug), "QMultiMap", map);
}
template <class Key, class T>
inline QDebugIfHasDebugStreamContainer<QHash<Key, T>, Key, T> operator<<(QDebug debug, const QHash<Key, T> &hash)
{
- return QtPrivate::printAssociativeContainer(debug, "QHash", hash);
+ return QtPrivate::printAssociativeContainer(std::move(debug), "QHash", hash);
}
template <class Key, class T>
inline QDebugIfHasDebugStreamContainer<QMultiHash<Key, T>, Key, T> operator<<(QDebug debug, const QMultiHash<Key, T> &hash)
{
- return QtPrivate::printAssociativeContainer(debug, "QMultiHash", hash);
+ return QtPrivate::printAssociativeContainer(std::move(debug), "QMultiHash", hash);
+}
+
+template <class T>
+inline QDebugIfHasDebugStream<T> operator<<(QDebug debug, const std::optional<T> &opt)
+{
+ const QDebugStateSaver saver(debug);
+ if (!opt)
+ debug.nospace() << std::nullopt;
+ else
+ debug.nospace() << "std::optional(" << *opt << ')';
+ return debug;
}
template <class T1, class T2>
@@ -366,7 +403,7 @@ inline QDebugIfHasDebugStream<T1, T2> operator<<(QDebug debug, const std::pair<T
template <typename T>
inline QDebugIfHasDebugStreamContainer<QSet<T>, T> operator<<(QDebug debug, const QSet<T> &set)
{
- return QtPrivate::printSequentialContainer(debug, "QSet", set);
+ return QtPrivate::printSequentialContainer(std::move(debug), "QSet", set);
}
template <class T>
@@ -418,9 +455,6 @@ template <typename T>
QDebug operator<<(QDebug debug, const QSet<T> &set);
template <class T1, class T2>
-QDebug operator<<(QDebug debug, const QPair<T1, T2> &pair);
-
-template <class T1, class T2>
QDebug operator<<(QDebug debug, const std::pair<T1, T2> &pair);
template <typename T>
diff --git a/src/corelib/io/qdir.cpp b/src/corelib/io/qdir.cpp
index 3ad804b9e8..05947f3380 100644
--- a/src/corelib/io/qdir.cpp
+++ b/src/corelib/io/qdir.cpp
@@ -9,7 +9,7 @@
#ifndef QT_NO_DEBUG_STREAM
#include "qdebug.h"
#endif
-#include "qdiriterator.h"
+#include "qdirlisting.h"
#include "qdatetime.h"
#include "qstring.h"
#if QT_CONFIG(regularexpression)
@@ -313,9 +313,10 @@ inline void QDirPrivate::sortFileList(QDir::SortFlags sort, const QFileInfoList
names->append(fi.fileName());
}
} else {
- QScopedArrayPointer<QDirSortItem> si(new QDirSortItem[n]);
+ QVarLengthArray<QDirSortItem, 64> si;
+ si.reserve(n);
for (qsizetype i = 0; i < n; ++i)
- si[i] = QDirSortItem{l.at(i), sort};
+ si.emplace_back(l.at(i), sort);
#ifndef QT_BOOTSTRAPPED
if (sort.testAnyFlag(QDir::LocaleAware)) {
@@ -346,9 +347,8 @@ inline void QDirPrivate::initFileLists(const QDir &dir) const
QMutexLocker locker(&fileCache.mutex);
if (!fileCache.fileListsInitialized) {
QFileInfoList l;
- QDirIterator it(dir);
- while (it.hasNext())
- l.append(it.nextFileInfo());
+ for (const auto &dirEntry : QDirListing(dir))
+ l.emplace_back(dirEntry.fileInfo());
sortFileList(sort, l, &fileCache.files, &fileCache.fileInfos);
fileCache.fileListsInitialized = true;
@@ -363,8 +363,7 @@ inline void QDirPrivate::clearCache(MetaDataClearing mode)
fileCache.fileListsInitialized = false;
fileCache.files.clear();
fileCache.fileInfos.clear();
- fileEngine.reset(
- QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, fileCache.metaData));
+ fileEngine = QFileSystemEngine::createLegacyEngine(dirEntry, fileCache.metaData);
}
/*!
@@ -376,6 +375,7 @@ inline void QDirPrivate::clearCache(MetaDataClearing mode)
\ingroup shared
\reentrant
+ \compares equality
A QDir is used to manipulate path names, access information
regarding paths and files, and manipulate the underlying file
@@ -1294,6 +1294,7 @@ QDir::SortFlags QDir::sorting() const
after the directories, again in reverse order.
*/
+#ifndef QT_BOOTSTRAPPED
/*!
Sets the sort order used by entryList() and entryInfoList().
@@ -1426,18 +1427,16 @@ QStringList QDir::entryList(const QStringList &nameFilters, Filters filters,
}
}
- QDirIterator it(d->dirEntry.filePath(), nameFilters, filters);
+ QDirListing dirList(d->dirEntry.filePath(), nameFilters, filters);
QStringList ret;
if (needsSorting) {
QFileInfoList l;
- while (it.hasNext())
- l.append(it.nextFileInfo());
+ for (const auto &dirEntry : dirList)
+ l.emplace_back(dirEntry.fileInfo());
d->sortFileList(sort, l, &ret, nullptr);
} else {
- while (it.hasNext()) {
- it.next();
- ret.append(it.fileName());
- }
+ for (const auto &dirEntry : dirList)
+ ret.emplace_back(dirEntry.fileName());
}
return ret;
}
@@ -1474,13 +1473,13 @@ QFileInfoList QDir::entryInfoList(const QStringList &nameFilters, Filters filter
}
QFileInfoList l;
- QDirIterator it(d->dirEntry.filePath(), nameFilters, filters);
- while (it.hasNext())
- l.append(it.nextFileInfo());
+ for (const auto &dirEntry : QDirListing(d->dirEntry.filePath(), nameFilters, filters))
+ l.emplace_back(dirEntry.fileInfo());
QFileInfoList ret;
d->sortFileList(sort, l, nullptr, &ret);
return ret;
}
+#endif // !QT_BOOTSTRAPPED
/*!
Creates a sub-directory called \a dirName.
@@ -1617,6 +1616,7 @@ bool QDir::rmpath(const QString &dirPath) const
return d->fileEngine->rmdir(fn, true);
}
+#ifndef QT_BOOTSTRAPPED
/*!
\since 5.0
Removes the directory, including all its contents.
@@ -1645,12 +1645,11 @@ bool QDir::removeRecursively()
bool success = true;
const QString dirPath = path();
// not empty -- we must empty it first
- QDirIterator di(dirPath, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
- while (di.hasNext()) {
- const QFileInfo fi = di.nextFileInfo();
- const QString &filePath = di.filePath();
+ constexpr auto dirFilters = QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot;
+ for (const auto &dirEntry : QDirListing(dirPath, dirFilters)) {
+ const QString &filePath = dirEntry.filePath();
bool ok;
- if (fi.isDir() && !fi.isSymLink()) {
+ if (dirEntry.isDir() && !dirEntry.isSymLink()) {
ok = QDir(filePath).removeRecursively(); // recursive
} else {
ok = QFile::remove(filePath);
@@ -1670,6 +1669,7 @@ bool QDir::removeRecursively()
return success;
}
+#endif // !QT_BOOTSTRAPPED
/*!
Returns \c true if the directory is readable \e and we can open files
@@ -1806,7 +1806,9 @@ bool QDir::makeAbsolute()
}
/*!
- Returns \c true if directory \a dir and this directory have the same
+ \fn bool QDir::operator==(const QDir &lhs, const QDir &rhs)
+
+ Returns \c true if directory \a lhs and directory \a rhs have the same
path and their sort and filter settings are the same; otherwise
returns \c false.
@@ -1814,10 +1816,10 @@ bool QDir::makeAbsolute()
\snippet code/src_corelib_io_qdir.cpp 10
*/
-bool QDir::operator==(const QDir &dir) const
+bool comparesEqual(const QDir &lhs, const QDir &rhs)
{
- Q_D(const QDir);
- const QDirPrivate *other = dir.d_ptr.constData();
+ const QDirPrivate *d = lhs.d_ptr.constData();
+ const QDirPrivate *other = rhs.d_ptr.constData();
if (d == other)
return true;
@@ -1841,13 +1843,13 @@ bool QDir::operator==(const QDir &dir) const
if (d->dirEntry.filePath() == other->dirEntry.filePath())
return true;
- if (exists()) {
- if (!dir.exists())
+ if (lhs.exists()) {
+ if (!rhs.exists())
return false; //can't be equal if only one exists
// Both exist, fallback to expensive canonical path computation
- return canonicalPath().compare(dir.canonicalPath(), sensitive) == 0;
+ return lhs.canonicalPath().compare(rhs.canonicalPath(), sensitive) == 0;
} else {
- if (dir.exists())
+ if (rhs.exists())
return false; //can't be equal if only one exists
// Neither exists, compare absolute paths rather than canonical (which would be empty strings)
QString thisFilePath = d->resolveAbsoluteEntry();
@@ -1877,11 +1879,10 @@ QDir &QDir::operator=(const QDir &dir)
*/
/*!
- \fn bool QDir::operator!=(const QDir &dir) const
+ \fn bool QDir::operator!=(const QDir &lhs, const QDir &rhs)
- Returns \c true if directory \a dir and this directory have different
- paths or different sort or filter settings; otherwise returns
- false.
+ Returns \c true if directory \a lhs and directory \a rhs have different
+ paths or different sort or filter settings; otherwise returns \c false.
Example:
@@ -1951,6 +1952,7 @@ bool QDir::exists(const QString &name) const
return QFileInfo::exists(filePath(name));
}
+#ifndef QT_BOOTSTRAPPED
/*!
Returns whether the directory is empty.
@@ -1967,9 +1969,10 @@ bool QDir::exists(const QString &name) const
bool QDir::isEmpty(Filters filters) const
{
Q_D(const QDir);
- QDirIterator it(d->dirEntry.filePath(), d->nameFilters, filters);
- return !it.hasNext();
+ QDirListing dirList(d->dirEntry.filePath(), d->nameFilters, filters);
+ return dirList.cbegin() == dirList.cend();
}
+#endif // !QT_BOOTSTRAPPED
/*!
Returns a list of the root directories on this system.
@@ -2356,7 +2359,7 @@ QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormaliza
// If path was not modified return the original value
if (used == 0)
return name;
- return QString::fromUtf16(out + used, len - used);
+ return QStringView(out + used, len - used).toString();
}
static QString qt_cleanPath(const QString &path, bool *ok)
diff --git a/src/corelib/io/qdir.h b/src/corelib/io/qdir.h
index 7d5e940e84..53c0900f95 100644
--- a/src/corelib/io/qdir.h
+++ b/src/corelib/io/qdir.h
@@ -4,6 +4,7 @@
#ifndef QDIR_H
#define QDIR_H
+#include <QtCore/qcompare.h>
#include <QtCore/qstring.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
@@ -185,8 +186,10 @@ public:
inline bool isAbsolute() const { return !isRelative(); }
bool makeAbsolute();
+#if QT_CORE_REMOVED_SINCE(6, 8)
bool operator==(const QDir &dir) const;
inline bool operator!=(const QDir &dir) const { return !operator==(dir); }
+#endif
bool remove(const QString &fileName);
bool rename(const QString &oldName, const QString &newName);
@@ -237,7 +240,10 @@ protected:
QSharedDataPointer<QDirPrivate> d_ptr;
private:
+ friend Q_CORE_EXPORT bool comparesEqual(const QDir &lhs, const QDir &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QDir)
friend class QDirIterator;
+ friend class QDirListing;
// Q_DECLARE_PRIVATE equivalent for shared data pointers
QDirPrivate *d_func();
const QDirPrivate *d_func() const { return d_ptr.constData(); }
diff --git a/src/corelib/io/qdirentryinfo_p.h b/src/corelib/io/qdirentryinfo_p.h
new file mode 100644
index 0000000000..7ed5391ff0
--- /dev/null
+++ b/src/corelib/io/qdirentryinfo_p.h
@@ -0,0 +1,156 @@
+// Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QDIRENTRYINFO_P_H
+#define QDIRENTRYINFO_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qfileinfo_p.h>
+#include <QtCore/private/qfilesystementry_p.h>
+#include <QtCore/private/qfilesystemmetadata_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QDirEntryInfo
+{
+ const QFileSystemMetaData &ensureFilled(QFileSystemMetaData::MetaDataFlags what)
+ {
+ if (!metaData.hasFlags(what))
+ QFileSystemEngine::fillMetaData(entry, metaData, what);
+ return metaData;
+ }
+
+public:
+ const QFileInfo &fileInfo()
+ {
+ if (!fileInfoOpt) {
+ fileInfoOpt.emplace(new QFileInfoPrivate(entry, metaData));
+ metaData.clear();
+ }
+ return *fileInfoOpt;
+ }
+
+ QString fileName()
+ { return fileInfoOpt ? fileInfoOpt->fileName() : entry.fileName(); }
+ QString baseName()
+ { return fileInfoOpt ? fileInfoOpt->baseName() : entry.baseName(); }
+ QString completeBaseName() const
+ { return fileInfoOpt ? fileInfoOpt->completeBaseName() : entry.completeBaseName(); }
+ QString suffix() const
+ { return fileInfoOpt ? fileInfoOpt->suffix() : entry.suffix(); }
+ QString completeSuffix() const
+ { return fileInfoOpt ? fileInfoOpt->completeSuffix() : entry.completeSuffix(); }
+ QString filePath()
+ { return fileInfoOpt ? fileInfoOpt->filePath() : entry.filePath(); }
+
+ QString bundleName() { return fileInfo().bundleName(); }
+
+ QString canonicalFilePath()
+ {
+ // QFileInfo caches these strings
+ return fileInfo().canonicalFilePath();
+ }
+
+ QString absoluteFilePath() {
+ // QFileInfo caches these strings
+ return fileInfo().absoluteFilePath();
+ }
+
+ QString absolutePath() {
+ // QFileInfo caches these strings
+ return fileInfo().absolutePath();
+ }
+
+
+ bool isDir() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isDir();
+
+ return ensureFilled(QFileSystemMetaData::DirectoryType).isDirectory();
+ }
+
+ bool isFile() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isFile();
+
+ return ensureFilled(QFileSystemMetaData::FileType).isFile();
+ }
+
+ bool isSymLink() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isSymLink();
+
+ return ensureFilled(QFileSystemMetaData::LegacyLinkType).isLegacyLink();
+ }
+
+ bool isSymbolicLink() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isSymbolicLink();
+
+ return ensureFilled(QFileSystemMetaData::LinkType).isLink();
+ }
+
+ bool exists() {
+ if (fileInfoOpt)
+ return fileInfoOpt->exists();
+
+ return ensureFilled(QFileSystemMetaData::ExistsAttribute).exists();
+ }
+
+ bool isHidden() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isHidden();
+
+ return ensureFilled(QFileSystemMetaData::HiddenAttribute).isHidden();
+ }
+
+ bool isReadable() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isReadable();
+
+ return ensureFilled(QFileSystemMetaData::UserReadPermission).isReadable();
+ }
+
+ bool isWritable() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isWritable();
+
+ return ensureFilled(QFileSystemMetaData::UserWritePermission).isWritable();
+ }
+
+ bool isExecutable() {
+ if (fileInfoOpt)
+ return fileInfoOpt->isExecutable();
+
+ return ensureFilled(QFileSystemMetaData::UserExecutePermission).isExecutable();
+ }
+
+ qint64 size() { return fileInfo().size(); }
+
+ QDateTime fileTime(QFile::FileTime type, const QTimeZone &tz)
+ {
+ return fileInfo().fileTime(type, tz);
+ }
+
+private:
+ friend class QDirListingPrivate;
+ friend class QDirListing;
+
+ QFileSystemEntry entry;
+ QFileSystemMetaData metaData;
+ std::optional<QFileInfo> fileInfoOpt;
+};
+
+QT_END_NAMESPACE
+
+#endif // QDIRENTRYINFO_P_H
diff --git a/src/corelib/io/qdiriterator.cpp b/src/corelib/io/qdiriterator.cpp
index 101c8fd2e7..3604e673e2 100644
--- a/src/corelib/io/qdiriterator.cpp
+++ b/src/corelib/io/qdiriterator.cpp
@@ -34,6 +34,9 @@
you cannot iterate directories in reverse order) and does not allow random
access.
+ \note This class is deprecated and may be removed in a Qt release. Use
+ QDirListing instead.
+
\sa QDir, QDir::entryList()
*/
@@ -56,6 +59,8 @@
#include "qdiriterator.h"
#include "qdir_p.h"
#include "qabstractfileengine_p.h"
+#include "qdirlisting.h"
+#include "qdirentryinfo_p.h"
#include <QtCore/qset.h>
#include <QtCore/qstack.h>
@@ -72,297 +77,74 @@
#include <QtCore/private/qduplicatetracker_p.h>
#include <memory>
+#include <stack>
+#include <vector>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-template <class Iterator>
-class QDirIteratorPrivateIteratorStack : public QStack<Iterator *>
-{
-public:
- ~QDirIteratorPrivateIteratorStack()
- {
- qDeleteAll(*this);
- }
-};
-
class QDirIteratorPrivate
{
-public:
- QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
- QDir::Filters _filters, QDirIterator::IteratorFlags flags, bool resolveEngine = true);
-
- void advance();
-
- bool entryMatches(const QString & fileName, const QFileInfo &fileInfo);
- void pushDirectory(const QFileInfo &fileInfo);
- void checkAndPushDirectory(const QFileInfo &);
- bool matchesFilters(const QString &fileName, const QFileInfo &fi) const;
-
- std::unique_ptr<QAbstractFileEngine> engine;
-
- QFileSystemEntry dirEntry;
- const QStringList nameFilters;
- const QDir::Filters filters;
- const QDirIterator::IteratorFlags iteratorFlags;
-
-#if QT_CONFIG(regularexpression)
- QList<QRegularExpression> nameRegExps;
-#endif
-
- QDirIteratorPrivateIteratorStack<QAbstractFileEngineIterator> fileEngineIterators;
-#ifndef QT_NO_FILESYSTEMITERATOR
- QDirIteratorPrivateIteratorStack<QFileSystemIterator> nativeIterators;
-#endif
-
- QFileInfo currentFileInfo;
- QFileInfo nextFileInfo;
+ static QDirListing::IteratorFlags toDirListingFlags(QDirIterator::IteratorFlags flags)
+ {
+ using F = QDirListing::IteratorFlag;
+ QDirListing::IteratorFlags listerFlags;
- // Loop protection
- QDuplicateTracker<QString> visitedLinks;
-};
+ if (flags & QDirIterator::NoIteratorFlags)
+ listerFlags.setFlag(F::NoFlag);
+ if (flags & QDirIterator::FollowSymlinks)
+ listerFlags.setFlag(F::FollowSymlinks);
+ if (flags & QDirIterator::Subdirectories)
+ listerFlags.setFlag(F::Recursive);
-/*!
- \internal
-*/
-QDirIteratorPrivate::QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
- QDir::Filters _filters, QDirIterator::IteratorFlags flags, bool resolveEngine)
- : dirEntry(entry)
- , nameFilters(nameFilters.contains("*"_L1) ? QStringList() : nameFilters)
- , filters(QDir::NoFilter == _filters ? QDir::AllEntries : _filters)
- , iteratorFlags(flags)
-{
-#if QT_CONFIG(regularexpression)
- nameRegExps.reserve(nameFilters.size());
- for (const auto &filter : nameFilters) {
- auto re = QRegularExpression::fromWildcard(filter, (filters & QDir::CaseSensitive ?
- Qt::CaseSensitive : Qt::CaseInsensitive));
- nameRegExps.append(re);
+ return listerFlags;
}
-#endif
- QFileSystemMetaData metaData;
- if (resolveEngine)
- engine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData));
- QFileInfo fileInfo(new QFileInfoPrivate(dirEntry, metaData));
-
- // Populate fields for hasNext() and next()
- pushDirectory(fileInfo);
- advance();
-}
-
-/*!
- \internal
-*/
-void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo)
-{
- QString path = fileInfo.filePath();
-
-#ifdef Q_OS_WIN
- if (fileInfo.isSymLink())
- path = fileInfo.canonicalFilePath();
-#endif
- if ((iteratorFlags & QDirIterator::FollowSymlinks)) {
- // Stop link loops
- if (visitedLinks.hasSeen(fileInfo.canonicalFilePath()))
- return;
+public:
+ QDirIteratorPrivate(const QDir &dir, QDirIterator::IteratorFlags flags)
+ : lister(dir, toDirListingFlags(flags))
+ {
+ init();
}
-
- if (engine) {
- engine->setFileName(path);
- QAbstractFileEngineIterator *it = engine->beginEntryList(filters, nameFilters);
- if (it) {
- it->setPath(path);
- fileEngineIterators << it;
- } else {
- // No iterator; no entry list.
- }
- } else {
-#ifndef QT_NO_FILESYSTEMITERATOR
- QFileSystemIterator *it = new QFileSystemIterator(fileInfo.d_ptr->fileEntry,
- filters, nameFilters, iteratorFlags);
- nativeIterators << it;
-#else
- qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!");
-#endif
+ QDirIteratorPrivate(const QString &path, QDirIterator::IteratorFlags flags)
+ : lister(path, toDirListingFlags(flags))
+ {
+ init();
}
-}
-
-inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QFileInfo &fileInfo)
-{
- checkAndPushDirectory(fileInfo);
-
- if (matchesFilters(fileName, fileInfo)) {
- currentFileInfo = nextFileInfo;
- nextFileInfo = fileInfo;
-
- //We found a matching entry.
- return true;
+ QDirIteratorPrivate(const QString &path, QDir::Filters filters,
+ QDirIterator::IteratorFlags flags)
+ : lister(path, filters, toDirListingFlags(flags))
+ {
+ init();
}
-
- return false;
-}
-
-/*!
- \internal
-*/
-void QDirIteratorPrivate::advance()
-{
- if (engine) {
- while (!fileEngineIterators.isEmpty()) {
- // Find the next valid iterator that matches the filters.
- QAbstractFileEngineIterator *it;
- while (it = fileEngineIterators.top(), it->hasNext()) {
- it->next();
- if (entryMatches(it->currentFileName(), it->currentFileInfo()))
- return;
- }
-
- fileEngineIterators.pop();
- delete it;
- }
- } else {
-#ifndef QT_NO_FILESYSTEMITERATOR
- QFileSystemEntry nextEntry;
- QFileSystemMetaData nextMetaData;
-
- while (!nativeIterators.isEmpty()) {
- // Find the next valid iterator that matches the filters.
- QFileSystemIterator *it;
- while (it = nativeIterators.top(), it->advance(nextEntry, nextMetaData)) {
- QFileInfo info(new QFileInfoPrivate(nextEntry, nextMetaData));
-
- if (entryMatches(nextEntry.fileName(), info))
- return;
- nextMetaData = QFileSystemMetaData();
- }
-
- nativeIterators.pop();
- delete it;
- }
-#endif
+ QDirIteratorPrivate(const QString &path, const QStringList &nameFilters, QDir::Filters filters,
+ QDirIterator::IteratorFlags flags)
+ : lister(path, nameFilters, filters, toDirListingFlags(flags))
+ {
+ init();
}
- currentFileInfo = nextFileInfo;
- nextFileInfo = QFileInfo();
-}
-
-/*!
- \internal
- */
-void QDirIteratorPrivate::checkAndPushDirectory(const QFileInfo &fileInfo)
-{
- // If we're doing flat iteration, we're done.
- if (!(iteratorFlags & QDirIterator::Subdirectories))
- return;
-
- // Never follow non-directory entries
- if (!fileInfo.isDir())
- return;
-
- // Follow symlinks only when asked
- if (!(iteratorFlags & QDirIterator::FollowSymlinks) && fileInfo.isSymLink())
- return;
-
- // Never follow . and ..
- QString fileName = fileInfo.fileName();
- if ("."_L1 == fileName || ".."_L1 == fileName)
- return;
-
- // No hidden directories unless requested
- if (!(filters & QDir::AllDirs) && !(filters & QDir::Hidden) && fileInfo.isHidden())
- return;
-
- pushDirectory(fileInfo);
-}
-
-/*!
- \internal
-
- This convenience function implements the iterator's filtering logics and
- applies then to the current directory entry.
-
- It returns \c true if the current entry matches the filters (i.e., the
- current entry will be returned as part of the directory iteration);
- otherwise, false is returned.
-*/
-
-bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInfo &fi) const
-{
- if (fileName.isEmpty())
- return false;
-
- // filter . and ..?
- const qsizetype fileNameSize = fileName.size();
- const bool dotOrDotDot = fileName[0] == u'.'
- && ((fileNameSize == 1)
- ||(fileNameSize == 2 && fileName[1] == u'.'));
- if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1)
- return false;
- if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2)
- return false;
-
- // name filter
-#if QT_CONFIG(regularexpression)
- // Pass all entries through name filters, except dirs if the AllDirs
- if (!nameFilters.isEmpty() && !((filters & QDir::AllDirs) && fi.isDir())) {
- bool matched = false;
- for (const auto &re : nameRegExps) {
- if (re.match(fileName).hasMatch()) {
- matched = true;
- break;
- }
- }
- if (!matched)
- return false;
- }
-#endif
- // skip symlinks
- const bool skipSymlinks = filters.testAnyFlag(QDir::NoSymLinks);
- const bool includeSystem = filters.testAnyFlag(QDir::System);
- if (skipSymlinks && fi.isSymLink()) {
- // The only reason to save this file is if it is a broken link and we are requesting system files.
- if (!includeSystem || fi.exists())
- return false;
+ void init()
+ {
+ it = lister.begin();
+ if (it != lister.end())
+ nextFileInfo = it->fileInfo();
}
- // filter hidden
- const bool includeHidden = filters.testAnyFlag(QDir::Hidden);
- if (!includeHidden && !dotOrDotDot && fi.isHidden())
- return false;
-
- // filter system files
- if (!includeSystem && (!(fi.isFile() || fi.isDir() || fi.isSymLink())
- || (!fi.exists() && fi.isSymLink())))
- return false;
-
- // skip directories
- const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
- if (skipDirs && fi.isDir())
- return false;
-
- // skip files
- const bool skipFiles = !(filters & QDir::Files);
- if (skipFiles && fi.isFile())
- // Basically we need a reason not to exclude this file otherwise we just eliminate it.
- return false;
-
- // filter permissions
- const bool filterPermissions = ((filters & QDir::PermissionMask)
- && (filters & QDir::PermissionMask) != QDir::PermissionMask);
- const bool doWritable = !filterPermissions || (filters & QDir::Writable);
- const bool doExecutable = !filterPermissions || (filters & QDir::Executable);
- const bool doReadable = !filterPermissions || (filters & QDir::Readable);
- if (filterPermissions
- && ((doReadable && !fi.isReadable())
- || (doWritable && !fi.isWritable())
- || (doExecutable && !fi.isExecutable()))) {
- return false;
+ void advance()
+ {
+ currentFileInfo = nextFileInfo;
+ if (++it != lister.end()) {
+ nextFileInfo = it->fileInfo();
+ }
}
- return true;
-}
+ QDirListing lister;
+ QDirListing::const_iterator it = {};
+ QFileInfo currentFileInfo;
+ QFileInfo nextFileInfo;
+};
/*!
Constructs a QDirIterator that can iterate over \a dir's entrylist, using
@@ -380,9 +162,8 @@ bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInf
\sa hasNext(), next(), IteratorFlags
*/
QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
+ : d(new QDirIteratorPrivate(dir, flags))
{
- const QDirPrivate *other = dir.d_ptr.constData();
- d.reset(new QDirIteratorPrivate(other->dirEntry, other->nameFilters, other->filters, flags, bool(other->fileEngine)));
}
/*!
@@ -399,7 +180,7 @@ QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
\sa hasNext(), next(), IteratorFlags
*/
QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags)
- : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), filters, flags))
+ : d(new QDirIteratorPrivate(path, filters, flags))
{
}
@@ -416,7 +197,7 @@ QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorF
\sa hasNext(), next(), IteratorFlags
*/
QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
- : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), QDir::NoFilter, flags))
+ : d(new QDirIteratorPrivate(path, flags))
{
}
@@ -440,7 +221,7 @@ QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
*/
QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
QDir::Filters filters, IteratorFlags flags)
- : d(new QDirIteratorPrivate(QFileSystemEntry(path), nameFilters, filters, flags))
+ : d(new QDirIteratorPrivate(path, nameFilters, filters, flags))
{
}
@@ -466,7 +247,7 @@ QDirIterator::~QDirIterator()
QString QDirIterator::next()
{
d->advance();
- return filePath();
+ return d->currentFileInfo.filePath();
}
/*!
@@ -486,7 +267,7 @@ QString QDirIterator::next()
QFileInfo QDirIterator::nextFileInfo()
{
d->advance();
- return fileInfo();
+ return d->currentFileInfo;
}
/*!
@@ -497,14 +278,7 @@ QFileInfo QDirIterator::nextFileInfo()
*/
bool QDirIterator::hasNext() const
{
- if (d->engine)
- return !d->fileEngineIterators.isEmpty();
- else
-#ifndef QT_NO_FILESYSTEMITERATOR
- return !d->nativeIterators.isEmpty();
-#else
- return false;
-#endif
+ return d->it != d->lister.end();
}
/*!
@@ -547,7 +321,7 @@ QFileInfo QDirIterator::fileInfo() const
*/
QString QDirIterator::path() const
{
- return d->dirEntry.filePath();
+ return d->lister.iteratorPath();
}
QT_END_NAMESPACE
diff --git a/src/corelib/io/qdirlisting.cpp b/src/corelib/io/qdirlisting.cpp
new file mode 100644
index 0000000000..7366aadf06
--- /dev/null
+++ b/src/corelib/io/qdirlisting.cpp
@@ -0,0 +1,696 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+/*!
+ \since 6.8
+ \class QDirListing
+ \inmodule QtCore
+ \brief The QDirListing class provides an STL-style iterator for directory entries.
+
+ You can use QDirListing to navigate entries of a directory one at a time.
+ It is similar to QDir::entryList() and QDir::entryInfoList(), but because
+ it lists entries one at a time instead of all at once, it scales better
+ and is more suitable for large directories. It also supports listing
+ directory contents recursively, and following symbolic links. Unlike
+ QDir::entryList(), QDirListing does not support sorting.
+
+ The QDirListing constructor takes a QDir or a directory path as
+ argument. Here's how to iterate over all entries recursively:
+
+ \snippet code/src_corelib_io_qdirlisting.cpp 0
+
+ Here's how to find and read all files filtered by name, recursively:
+
+ \snippet code/src_corelib_io_qdirlisting.cpp 1
+
+ Iterators constructed by QDirListing (QDirListing::const_iterator) are
+ forward-only, single-pass iterators, that don't allow random access. They
+ can be used in ranged-for loops (or with STL alogrithms that don't
+ require random access iterators). Dereferencing a valid iterator returns
+ a QDirListing::DirEntry object. The (c)end() iterator marks the end of
+ the iteration. Dereferencing the end iterator is undefined behavior.
+
+ QDirListing::DirEntry offers a subset of QFileInfo's API (for example,
+ fileName(), filePath(), exists()). Internally, DirEntry only constructs
+ a QFileInfo object if needed, that is, if the info hasn't been already
+ fetched by other system functions. You can use DirEntry::fileInfo()
+ to get a QFileInfo. For example:
+
+ \snippet code/src_corelib_io_qdirlisting.cpp 3
+ \snippet code/src_corelib_io_qdirlisting.cpp 4
+
+ \sa QDir, QDir::entryList()
+*/
+
+/*! \enum QDirListing::IteratorFlag
+
+ This enum class describes flags can be used to configure the behavior of
+ QDirListing. These flags can be bitwise OR'ed together.
+
+ \value NoFlag The default value, representing no flags. The iterator
+ will return entries for the assigned path.
+
+ \value FollowSymlinks When combined with Recursive, this flag enables
+ iterating through all subdirectories of the assigned path, following
+ all symbolic links. Symbolic link loops (e.g., link => . or link =>
+ ..) are automatically detected and ignored.
+
+ \value Recursive List entries inside all subdirectories as well.
+*/
+
+#include "qdirlisting.h"
+#include "qdirentryinfo_p.h"
+
+#include "qdir_p.h"
+#include "qabstractfileengine_p.h"
+
+#include <QtCore/qset.h>
+
+#if QT_CONFIG(regularexpression)
+#include <QtCore/qregularexpression.h>
+#endif
+
+#include <QtCore/private/qfilesystemiterator_p.h>
+#include <QtCore/private/qfilesystementry_p.h>
+#include <QtCore/private/qfilesystemmetadata_p.h>
+#include <QtCore/private/qfilesystemengine_p.h>
+#include <QtCore/private/qfileinfo_p.h>
+#include <QtCore/private/qduplicatetracker_p.h>
+
+#include <memory>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+class QDirListingPrivate
+{
+public:
+ void init(bool resolveEngine);
+ void advance();
+ void beginIterating();
+
+ bool entryMatches(QDirEntryInfo &info);
+ void pushDirectory(QDirEntryInfo &info);
+ void pushInitialDirectory();
+
+ void checkAndPushDirectory(QDirEntryInfo &info);
+ bool matchesFilters(QDirEntryInfo &data) const;
+ bool hasIterators() const;
+
+ std::unique_ptr<QAbstractFileEngine> engine;
+ QDirEntryInfo initialEntryInfo;
+ QStringList nameFilters;
+ QDir::Filters filters;
+ QDirListing::IteratorFlags iteratorFlags;
+ QDirEntryInfo currentEntryInfo;
+
+#if QT_CONFIG(regularexpression)
+ QList<QRegularExpression> nameRegExps;
+#endif
+
+ using FEngineIteratorPtr = std::unique_ptr<QAbstractFileEngineIterator>;
+ std::vector<FEngineIteratorPtr> fileEngineIterators;
+#ifndef QT_NO_FILESYSTEMITERATOR
+ using FsIteratorPtr = std::unique_ptr<QFileSystemIterator>;
+ std::vector<FsIteratorPtr> nativeIterators;
+#endif
+
+ // Loop protection
+ QDuplicateTracker<QString> visitedLinks;
+};
+
+void QDirListingPrivate::init(bool resolveEngine = true)
+{
+ if (nameFilters.contains("*"_L1))
+ nameFilters.clear();
+
+ if (filters == QDir::NoFilter)
+ filters = QDir::AllEntries;
+
+#if QT_CONFIG(regularexpression)
+ nameRegExps.reserve(nameFilters.size());
+ const auto cs = filters.testAnyFlags(QDir::CaseSensitive) ? Qt::CaseSensitive
+ : Qt::CaseInsensitive;
+ for (const auto &filter : nameFilters)
+ nameRegExps.emplace_back(QRegularExpression::fromWildcard(filter, cs));
+#endif
+
+ if (resolveEngine) {
+ engine = QFileSystemEngine::createLegacyEngine(initialEntryInfo.entry,
+ initialEntryInfo.metaData);
+ }
+}
+
+/*!
+ \internal
+
+ Resets the iteration state (if any), so that calling begin()/cbegin()
+ always starts iterating anew.
+*/
+void QDirListingPrivate::beginIterating()
+{
+#ifndef QT_NO_FILESYSTEMITERATOR
+ nativeIterators.clear();
+#endif
+ fileEngineIterators.clear();
+ visitedLinks.clear();
+ pushDirectory(initialEntryInfo);
+}
+
+void QDirListingPrivate::pushDirectory(QDirEntryInfo &entryInfo)
+{
+ const QString path = [&entryInfo] {
+#ifdef Q_OS_WIN
+ if (entryInfo.isSymLink())
+ return entryInfo.canonicalFilePath();
+#endif
+ return entryInfo.filePath();
+ }();
+
+
+ if (iteratorFlags.testAnyFlags(QDirListing::IteratorFlag::FollowSymlinks)) {
+ // Stop link loops
+ if (visitedLinks.hasSeen(entryInfo.canonicalFilePath()))
+ return;
+ }
+
+ if (engine) {
+ engine->setFileName(path);
+ if (auto it = engine->beginEntryList(path, filters, nameFilters)) {
+ fileEngineIterators.emplace_back(std::move(it));
+ } else {
+ // No iterator; no entry list.
+ }
+ } else {
+#ifndef QT_NO_FILESYSTEMITERATOR
+ QFileSystemEntry *fentry = nullptr;
+ if (entryInfo.fileInfoOpt)
+ fentry = &entryInfo.fileInfoOpt->d_ptr->fileEntry;
+ else
+ fentry = &entryInfo.entry;
+ nativeIterators.emplace_back(std::make_unique<QFileSystemIterator>(*fentry, filters));
+#else
+ qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!");
+#endif
+ }
+}
+
+bool QDirListingPrivate::entryMatches(QDirEntryInfo &entryInfo)
+{
+ checkAndPushDirectory(entryInfo);
+ return matchesFilters(entryInfo);
+}
+
+/*!
+ \internal
+
+ Advances the internal iterator, either a QAbstractFileEngineIterator (e.g.
+ QResourceFileEngineIterator) or a QFileSystemIterator (which uses low-level
+ system methods, e.g. readdir() on Unix). The iterators are stored in a
+ vector.
+
+ A typical example of doing recursive iteration:
+ - while iterating directory A we find a sub-dir B
+ - an iterator for B is added to the vector
+ - B's iterator is processed (vector.back()) first; then the loop
+ goes back to processing A's iterator
+*/
+void QDirListingPrivate::advance()
+{
+ // Use get() in both code paths below because the iterator returned by back()
+ // may be invalidated due to reallocation when appending new iterators in
+ // pushDirectory().
+
+ if (engine) {
+ while (!fileEngineIterators.empty()) {
+ // Find the next valid iterator that matches the filters.
+ QAbstractFileEngineIterator *it;
+ while (it = fileEngineIterators.back().get(), it->advance()) {
+ QDirEntryInfo entryInfo;
+ entryInfo.fileInfoOpt = it->currentFileInfo();
+ if (entryMatches(entryInfo)) {
+ currentEntryInfo = std::move(entryInfo);
+ return;
+ }
+ }
+
+ fileEngineIterators.pop_back();
+ }
+ } else {
+#ifndef QT_NO_FILESYSTEMITERATOR
+ QDirEntryInfo entryInfo;
+ while (!nativeIterators.empty()) {
+ // Find the next valid iterator that matches the filters.
+ QFileSystemIterator *it;
+ while (it = nativeIterators.back().get(),
+ it->advance(entryInfo.entry, entryInfo.metaData)) {
+ if (entryMatches(entryInfo)) {
+ currentEntryInfo = std::move(entryInfo);
+ return;
+ }
+ entryInfo = {};
+ }
+
+ nativeIterators.pop_back();
+ }
+#endif
+ }
+}
+
+void QDirListingPrivate::checkAndPushDirectory(QDirEntryInfo &entryInfo)
+{
+ using F = QDirListing::IteratorFlag;
+ // If we're doing flat iteration, we're done.
+ if (!iteratorFlags.testAnyFlags(F::Recursive))
+ return;
+
+ // Never follow non-directory entries
+ if (!entryInfo.isDir())
+ return;
+
+ // Follow symlinks only when asked
+ if (!iteratorFlags.testAnyFlags(F::FollowSymlinks) && entryInfo.isSymLink())
+ return;
+
+ // Never follow . and ..
+ const QString &fileName = entryInfo.fileName();
+ if ("."_L1 == fileName || ".."_L1 == fileName)
+ return;
+
+ // No hidden directories unless requested
+ if (!filters.testAnyFlags(QDir::AllDirs | QDir::Hidden) && entryInfo.isHidden())
+ return;
+
+ pushDirectory(entryInfo);
+}
+
+/*!
+ \internal
+
+ This functions returns \c true if the current entry matches the filters
+ (i.e., the current entry will be returned as part of the directory
+ iteration); otherwise, \c false is returned.
+*/
+bool QDirListingPrivate::matchesFilters(QDirEntryInfo &entryInfo) const
+{
+ const QString &fileName = entryInfo.fileName();
+ if (fileName.isEmpty())
+ return false;
+
+ // filter . and ..?
+ const qsizetype fileNameSize = fileName.size();
+ const bool dotOrDotDot = fileName[0] == u'.'
+ && ((fileNameSize == 1)
+ ||(fileNameSize == 2 && fileName[1] == u'.'));
+ if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1)
+ return false;
+ if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2)
+ return false;
+
+ // name filter
+#if QT_CONFIG(regularexpression)
+ // Pass all entries through name filters, except dirs if AllDirs is set
+ if (!nameRegExps.isEmpty() && !(filters.testAnyFlags(QDir::AllDirs) && entryInfo.isDir())) {
+ auto regexMatchesName = [&fileName](const auto &re) {
+ return re.match(fileName).hasMatch();
+ };
+ if (std::none_of(nameRegExps.cbegin(), nameRegExps.cend(), regexMatchesName))
+ return false;
+ }
+#endif
+ // skip symlinks
+ const bool skipSymlinks = filters.testAnyFlag(QDir::NoSymLinks);
+ const bool includeSystem = filters.testAnyFlag(QDir::System);
+ if (skipSymlinks && entryInfo.isSymLink()) {
+ // The only reason to save this file is if it is a broken link and we are requesting system files.
+ if (!includeSystem || entryInfo.exists())
+ return false;
+ }
+
+ // filter hidden
+ const bool includeHidden = filters.testAnyFlag(QDir::Hidden);
+ if (!includeHidden && !dotOrDotDot && entryInfo.isHidden())
+ return false;
+
+ // filter system files
+ if (!includeSystem) {
+ if (!entryInfo.isFile() && !entryInfo.isDir() && !entryInfo.isSymLink())
+ return false;
+ if (entryInfo.isSymLink() && !entryInfo.exists())
+ return false;
+ }
+
+ // skip directories
+ const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
+ if (skipDirs && entryInfo.isDir())
+ return false;
+
+ // skip files
+ const bool skipFiles = !(filters & QDir::Files);
+ if (skipFiles && entryInfo.isFile())
+ // Basically we need a reason not to exclude this file otherwise we just eliminate it.
+ return false;
+
+ // filter permissions
+ const auto perms = filters & QDir::PermissionMask;
+ const bool filterPermissions = perms != 0 && perms != QDir::PermissionMask;
+ if (filterPermissions) {
+ const bool doWritable = filters.testAnyFlags(QDir::Writable);
+ const bool doExecutable = filters.testAnyFlags(QDir::Executable);
+ const bool doReadable = filters.testAnyFlags(QDir::Readable);
+ if ((doReadable && !entryInfo.isReadable())
+ || (doWritable && !entryInfo.isWritable())
+ || (doExecutable && !entryInfo.isExecutable())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool QDirListingPrivate::hasIterators() const
+{
+ if (engine)
+ return !fileEngineIterators.empty();
+
+#if !defined(QT_NO_FILESYSTEMITERATOR)
+ return !nativeIterators.empty();
+#endif
+
+ return false;
+}
+
+/*!
+ Constructs a QDirListing that can iterate over \a dir's entries, using
+ \a dir's name filters and the QDir::Filters set in \a dir. You can pass
+ options via \a flags to decide how the directory should be iterated.
+
+ By default, \a flags is NoIteratorFlags, which provides the same behavior
+ as in QDir::entryList().
+
+ The sorting in \a dir is ignored.
+
+ \note To list symlinks that point to non existing files, QDir::System
+ must be set in \a dir's QDir::Filters.
+
+ \sa hasNext(), next(), IteratorFlags
+*/
+QDirListing::QDirListing(const QDir &dir, IteratorFlags flags)
+ : d(new QDirListingPrivate)
+{
+ const QDirPrivate *other = dir.d_ptr.constData();
+ d->initialEntryInfo.entry = other->dirEntry;
+ d->nameFilters = other->nameFilters;
+ d->filters = other->filters;
+ d->iteratorFlags = flags;
+ const bool resolveEngine = other->fileEngine ? true : false;
+ d->init(resolveEngine);
+}
+
+/*!
+ Constructs a QDirListing that can iterate over \a path. Entries are
+ filtered according to \a filters. You can pass options via \a flags to
+ decide how the directory should be iterated.
+
+ By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags,
+ which provides the same behavior as in QDir::entryList().
+
+ \note To list symlinks that point to non existing files, QDir::System
+ must be set in \a filters.
+
+ \sa hasNext(), next(), IteratorFlags
+*/
+QDirListing::QDirListing(const QString &path, QDir::Filters filters, IteratorFlags flags)
+ : d(new QDirListingPrivate)
+{
+ d->initialEntryInfo.entry = QFileSystemEntry(path);
+ d->filters = filters;
+ d->iteratorFlags = flags;
+ d->init();
+}
+
+/*!
+ Constructs a QDirListing that can iterate over \a path. You can pass
+ options via \a flags to decide how the directory should be iterated.
+
+ By default, \a flags is NoIteratorFlags, which provides the same behavior
+ as in QDir::entryList().
+
+ \sa hasNext(), next(), IteratorFlags
+*/
+QDirListing::QDirListing(const QString &path, IteratorFlags flags)
+ : d(new QDirListingPrivate)
+{
+ d->initialEntryInfo.entry = QFileSystemEntry(path);
+ d->filters = QDir::NoFilter;
+ d->iteratorFlags = flags;
+ d->init();
+}
+
+/*!
+ Constructs a QDirListing that can iterate over \a path, using \a
+ nameFilters and \a filters. You can pass options via \a flags to decide
+ how the directory should be iterated.
+
+ By default, \a flags is NoIteratorFlags, which provides the same behavior
+ as QDir::entryList().
+
+ For example, the following iterator could be used to iterate over audio
+ files:
+
+ \snippet code/src_corelib_io_qdirlisting.cpp 2
+
+ \note To list symlinks that point to non existing files, QDir::System
+ must be set in \a flags.
+
+ \sa hasNext(), next(), IteratorFlags, QDir::setNameFilters()
+*/
+QDirListing::QDirListing(const QString &path, const QStringList &nameFilters, QDir::Filters filters,
+ IteratorFlags flags)
+ : d(new QDirListingPrivate)
+{
+ d->initialEntryInfo.entry = QFileSystemEntry(path);
+ d->nameFilters = nameFilters;
+ d->filters = filters;
+ d->iteratorFlags = flags;
+ d->init();
+}
+
+/*!
+ Destroys the QDirListing.
+*/
+QDirListing::~QDirListing() = default;
+
+/*!
+ Returns the directory path used to construct this QDirListing.
+*/
+QString QDirListing::iteratorPath() const
+{
+ return d->initialEntryInfo.filePath();
+}
+
+/*!
+ \fn QDirListing::const_iterator QDirListing::begin() const
+ \fn QDirListing::const_iterator QDirListing::cbegin() const
+ \fn QDirListing::const_iterator QDirListing::end() const
+ \fn QDirListing::const_iterator QDirListing::cend() const
+
+ (c)begin() returns a QDirListing::const_iterator that can be used to
+ iterate over directory entries.
+
+ \list
+ \li This is a forward-only, single-pass iterator (you cannot iterate
+ directory entries in reverse order)
+ \li Doesn't allow random access
+ \li Can be used in ranged-for loops; or with STL algorithms that don't
+ require random access iterators
+ \li Dereferencing a valid iterator returns a \c{const DirEntry &}
+ \li (c)end() returns a sentinel-like const_iterator that signals the
+ end of the iteration. Dereferencing the end() iterator is undefined
+ behavior
+ \li Each time (c)begin() is called on the same QDirListing object,
+ the internal state is reset and the iteration starts anew
+ \endlist
+
+ (Some of the above restrictions are dictated by the underlying system
+ library functions' implementation).
+
+ For example:
+ \snippet code/src_corelib_io_qdirlisting.cpp 0
+
+ Here's how to find and read all files filtered by name, recursively:
+ \snippet code/src_corelib_io_qdirlisting.cpp 1
+
+ \sa fileInfo(), fileName(), filePath()
+*/
+QDirListing::const_iterator QDirListing::begin() const
+{
+ d->beginIterating();
+ const_iterator it{d.get()};
+ return ++it;
+}
+
+/*!
+ \fn const QDirListing::DirEntry &QDirListing::const_iterator::operator*() const
+
+ Returns a \c{const QDirListing::DirEntry &} of the directory entry this
+ iterator points to.
+*/
+
+/*!
+ \fn const QDirListing::DirEntry *QDirListing::const_iterator::operator->() const
+
+ Returns a \c{const QDirListing::DirEntry *} to the directory entry this
+ iterator points to.
+*/
+
+/*!
+ Advances the iterator and returns a reference to it.
+*/
+QDirListing::const_iterator &QDirListing::const_iterator::operator++()
+{
+ dirListPtr->advance();
+ if (!dirListPtr->hasIterators())
+ *this = {}; // All done, make `this` the end() iterator
+ return *this;
+}
+
+/*!
+ \fn QFileInfo QDirListing::DirEntry::fileInfo() const
+ \fn QString QDirListing::DirEntry::fileName() const
+ \fn QString QDirListing::DirEntry::baseName() const
+ \fn QString QDirListing::DirEntry::completeBaseName() const
+ \fn QString QDirListing::DirEntry::suffix() const
+ \fn QString QDirListing::DirEntry::bundleName() const
+ \fn QString QDirListing::DirEntry::completeSuffix() const
+ \fn QString QDirListing::DirEntry::filePath() const
+ \fn QString QDirListing::DirEntry::canonicalFilePath() const
+ \fn QString QDirListing::DirEntry::absoluteFilePath() const
+ \fn QString QDirListing::DirEntry::absolutePath() const
+ \fn bool QDirListing::DirEntry::isDir() const
+ \fn bool QDirListing::DirEntry::isFile() const
+ \fn bool QDirListing::DirEntry::isSymLink() const
+ \fn bool QDirListing::DirEntry::exists() const
+ \fn bool QDirListing::DirEntry::isHidden() const
+ \fn bool QDirListing::DirEntry::isReadable() const
+ \fn bool QDirListing::DirEntry::isWritable() const
+ \fn bool QDirListing::DirEntry::isExecutable() const
+ \fn qint64 QDirListing::DirEntry::size() const
+ \fn QDateTime QDirListing::DirEntry::fileTime(QFile::FileTime type, const QTimeZone &tz) const
+ \fn QDateTime QDirListing::DirEntry::birthTime(const QTimeZone &tz) const;
+ \fn QDateTime QDirListing::DirEntry::metadataChangeTime(const QTimeZone &tz) const;
+ \fn QDateTime QDirListing::DirEntry::lastModified(const QTimeZone &tz) const;
+ \fn QDateTime QDirListing::DirEntry::lastRead(const QTimeZone &tz) const;
+
+ See the QFileInfo methods with the same names.
+*/
+
+QFileInfo QDirListing::DirEntry::fileInfo() const
+{
+ return dirListPtr->currentEntryInfo.fileInfo();
+}
+
+QString QDirListing::DirEntry::fileName() const
+{
+ return dirListPtr->currentEntryInfo.fileName();
+}
+
+QString QDirListing::DirEntry::baseName() const
+{
+ return dirListPtr->currentEntryInfo.baseName();
+}
+
+QString QDirListing::DirEntry::completeBaseName() const
+{
+ return dirListPtr->currentEntryInfo.completeBaseName();
+}
+
+QString QDirListing::DirEntry::suffix() const
+{
+ return dirListPtr->currentEntryInfo.suffix();
+}
+
+QString QDirListing::DirEntry::bundleName() const
+{
+ return dirListPtr->currentEntryInfo.bundleName();
+}
+
+QString QDirListing::DirEntry::completeSuffix() const
+{
+ return dirListPtr->currentEntryInfo.completeSuffix();
+}
+
+QString QDirListing::DirEntry::filePath() const
+{
+ return dirListPtr->currentEntryInfo.filePath();
+}
+
+QString QDirListing::DirEntry::canonicalFilePath() const
+{
+ return dirListPtr->currentEntryInfo.canonicalFilePath();
+}
+
+QString QDirListing::DirEntry::absoluteFilePath() const
+{
+ return dirListPtr->currentEntryInfo.absoluteFilePath();
+}
+
+QString QDirListing::DirEntry::absolutePath() const
+{
+ return dirListPtr->currentEntryInfo.absolutePath();
+}
+
+bool QDirListing::DirEntry::isDir() const
+{
+ return dirListPtr->currentEntryInfo.isDir();
+}
+
+bool QDirListing::DirEntry::isFile() const
+{
+ return dirListPtr->currentEntryInfo.isFile();
+}
+
+bool QDirListing::DirEntry::isSymLink() const
+{
+ return dirListPtr->currentEntryInfo.isSymLink();
+}
+
+bool QDirListing::DirEntry::exists() const
+{
+ return dirListPtr->currentEntryInfo.exists();
+}
+
+bool QDirListing::DirEntry::isHidden() const
+{
+ return dirListPtr->currentEntryInfo.isHidden();
+}
+
+bool QDirListing::DirEntry::isReadable() const
+{
+ return dirListPtr->currentEntryInfo.isReadable();
+}
+
+bool QDirListing::DirEntry::isWritable() const
+{
+ return dirListPtr->currentEntryInfo.isWritable();
+}
+
+bool QDirListing::DirEntry::isExecutable() const
+{
+ return dirListPtr->currentEntryInfo.isExecutable();
+}
+
+qint64 QDirListing::DirEntry::size() const
+{
+ return dirListPtr->currentEntryInfo.size();
+}
+
+QDateTime QDirListing::DirEntry::fileTime(QFile::FileTime type, const QTimeZone &tz) const
+{
+ return dirListPtr->currentEntryInfo.fileTime(type, tz);
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/io/qdirlisting.h b/src/corelib/io/qdirlisting.h
new file mode 100644
index 0000000000..d19fe3c666
--- /dev/null
+++ b/src/corelib/io/qdirlisting.h
@@ -0,0 +1,119 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QDILISTING_H
+#define QDILISTING_H
+
+#include <QtCore/qdir.h>
+
+#include <iterator>
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+class QDirListingPrivate;
+
+class Q_CORE_EXPORT QDirListing
+{
+public:
+ enum class IteratorFlag {
+ NoFlag = 0x0,
+ FollowSymlinks = 0x1,
+ Recursive = 0x2
+ };
+ Q_DECLARE_FLAGS(IteratorFlags, IteratorFlag)
+
+ QDirListing(const QDir &dir, IteratorFlags flags = IteratorFlag::NoFlag);
+ QDirListing(const QString &path, IteratorFlags flags = IteratorFlag::NoFlag);
+ QDirListing(const QString &path, QDir::Filters filter,
+ IteratorFlags flags = IteratorFlag::NoFlag);
+ QDirListing(const QString &path, const QStringList &nameFilters,
+ QDir::Filters filters = QDir::NoFilter,
+ IteratorFlags flags = IteratorFlag::NoFlag);
+
+ ~QDirListing();
+
+ QString iteratorPath() const;
+
+ class Q_CORE_EXPORT DirEntry
+ {
+ friend class QDirListing;
+ QDirListingPrivate *dirListPtr = nullptr;
+ public:
+ QString fileName() const;
+ QString baseName() const;
+ QString completeBaseName() const;
+ QString suffix() const;
+ QString bundleName() const;
+ QString completeSuffix() const;
+ QString filePath() const;
+ bool isDir() const;
+ bool isFile() const;
+ bool isSymLink() const;
+ bool exists() const;
+ bool isHidden() const;
+ bool isReadable() const;
+ bool isWritable() const;
+ bool isExecutable() const;
+ QFileInfo fileInfo() const;
+ QString canonicalFilePath() const;
+ QString absoluteFilePath() const;
+ QString absolutePath() const;
+ qint64 size() const;
+
+ QDateTime birthTime(const QTimeZone &tz) const { return fileTime(QFile::FileBirthTime, tz); }
+ QDateTime metadataChangeTime(const QTimeZone &tz) const { return fileTime(QFile::FileMetadataChangeTime, tz); }
+ QDateTime lastModified(const QTimeZone &tz) const { return fileTime(QFile::FileModificationTime, tz); }
+ QDateTime lastRead(const QTimeZone &tz) const { return fileTime(QFile::FileAccessTime, tz); }
+ QDateTime fileTime(QFile::FileTime type, const QTimeZone &tz) const;
+ };
+
+ class const_iterator
+ {
+ friend class QDirListing;
+ const_iterator(QDirListingPrivate *dp) : dirListPtr(dp) { dirEntry.dirListPtr = dp; }
+ QDirListingPrivate *dirListPtr = nullptr;
+ DirEntry dirEntry;
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = DirEntry;
+ using difference_type = qint64;
+ using pointer = const value_type *;
+ using reference = const value_type &;
+
+ const_iterator() = default;
+ reference operator*() const { return dirEntry; }
+ pointer operator->() const { return &dirEntry; }
+ Q_CORE_EXPORT const_iterator &operator++();
+ const_iterator operator++(int) { auto tmp = *this; operator++(); return tmp; };
+ friend bool operator==(const const_iterator &lhs, const const_iterator &rhs)
+ {
+ // This is only used for the sentinel end iterator
+ return lhs.dirListPtr == nullptr && rhs.dirListPtr == nullptr;
+ }
+ friend bool operator!=(const const_iterator &lhs, const const_iterator &rhs)
+ { return !(lhs == rhs); }
+ };
+
+ const_iterator begin() const;
+ const_iterator cbegin() const { return begin(); }
+ const_iterator end() const { return {}; }
+ const_iterator cend() const { return end(); }
+
+ // Qt compatibility
+ const_iterator constBegin() const { return begin(); }
+ const_iterator constEnd() const { return end(); }
+
+private:
+ Q_DISABLE_COPY(QDirListing)
+
+ std::unique_ptr<QDirListingPrivate> d;
+ friend class QDir;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QDirListing::IteratorFlags)
+
+QT_END_NAMESPACE
+
+#endif // QDILISTING_H
diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp
index ec67ca6f48..ea594470ea 100644
--- a/src/corelib/io/qfile.cpp
+++ b/src/corelib/io/qfile.cpp
@@ -76,7 +76,7 @@ QFilePrivate::openExternalFile(QIODevice::OpenMode flags, FILE *fh, QFile::FileH
QAbstractFileEngine *QFilePrivate::engine() const
{
if (!fileEngine)
- fileEngine.reset(QAbstractFileEngine::create(fileName));
+ fileEngine = QAbstractFileEngine::create(fileName);
return fileEngine.get();
}
@@ -459,6 +459,24 @@ QFile::remove(const QString &fileName)
and sets the fileName() to the path at which the file can be found within the trash;
otherwise returns \c false.
+//! [move-to-trash-common]
+ The time for this function to run is independent of the size of the file
+ being trashed. If this function is called on a directory, it may be
+ proportional to the number of files being trashed.
+
+ This function uses the Windows and \macos APIs to perform the trashing on
+ those two operating systems. Elsewhere (Unix systems), this function
+ implements the \l{FreeDesktop.org Trash specification version 1.0}.
+
+ \note When using the FreeDesktop.org Trash implementation, this function
+ will fail if it is unable to move the files to the trash location by way of
+ file renames and hardlinks. This condition arises if the file being trashed
+ resides on a volume (mount point) on which the current user does not have
+ permission to create the \c{.Trash} directory, or with some unusual
+ filesystem types or configurations (such as sub-volumes that aren't
+ themselves mount points).
+//! [move-to-trash-common]
+
\note On systems where the system API doesn't report the location of the file in the
trash, fileName() will be set to the null string once the file has been moved. On
systems that don't have a trash can, this function always returns false.
@@ -492,13 +510,16 @@ QFile::moveToTrash()
\since 5.15
\overload
- Moves the file specified by fileName() to the trash. Returns \c true if successful,
+ Moves the file specified by \a fileName to the trash. Returns \c true if successful,
and sets \a pathInTrash (if provided) to the path at which the file can be found within
the trash; otherwise returns \c false.
+ \include qfile.cpp move-to-trash-common
+
\note On systems where the system API doesn't report the path of the file in the
trash, \a pathInTrash will be set to the null string once the file has been moved.
On systems that don't have a trash can, this function always returns false.
+
*/
bool
QFile::moveToTrash(const QString &fileName, QString *pathInTrash)
@@ -563,7 +584,7 @@ QFile::rename(const QString &newName)
return false;
}
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) && QT_CONFIG(temporaryfile)
// rename() on Linux simply does nothing when renaming "foo" to "Foo" on a case-insensitive
// FS, such as FAT32. Move the file away and rename in 2 steps to work around.
QTemporaryFileName tfn(d->fileName);
@@ -768,7 +789,7 @@ QFile::copy(const QString &newName)
d->setError(QFile::CopyError, tr("Cannot open %1 for input").arg(d->fileName));
} else {
const auto fileTemplate = "%1/qt_temp.XXXXXX"_L1;
-#ifdef QT_NO_TEMPORARYFILE
+#if !QT_CONFIG(temporaryfile)
QFile out(fileTemplate.arg(QFileInfo(newName).path()));
if (!out.open(QIODevice::ReadWrite))
error = true;
@@ -781,9 +802,9 @@ QFile::copy(const QString &newName)
}
#endif
if (error) {
+ d->setError(QFile::CopyError, tr("Cannot open for output: %1").arg(out.errorString()));
out.close();
close();
- d->setError(QFile::CopyError, tr("Cannot open for output: %1").arg(out.errorString()));
} else {
if (!d->engine()->cloneTo(out.d_func()->engine())) {
char block[4096];
@@ -820,7 +841,7 @@ QFile::copy(const QString &newName)
.arg(newName, out.errorString()));
}
}
-#ifdef QT_NO_TEMPORARYFILE
+#if !QT_CONFIG(temporaryfile)
if (error)
out.remove();
#else
@@ -876,6 +897,8 @@ QFile::copy(const QString &fileName, const QString &newName)
of the file name, otherwise, it won't be possible to create this
non-existing file.
+ \sa QT_USE_NODISCARD_FILE_OPEN
+
\sa QIODevice::OpenMode, setFileName()
*/
bool QFile::open(OpenMode mode)
@@ -920,7 +943,7 @@ bool QFile::open(OpenMode mode)
such permissions will generate warnings when the Security tab of the Properties dialog
is opened. Granting the group all permissions granted to others avoids such warnings.
- \sa QIODevice::OpenMode, setFileName()
+ \sa QIODevice::OpenMode, setFileName(), QT_USE_NODISCARD_FILE_OPEN
\since 6.3
*/
bool QFile::open(OpenMode mode, QFile::Permissions permissions)
@@ -977,7 +1000,7 @@ bool QFile::open(OpenMode mode, QFile::Permissions permissions)
you cannot use this QFile with a QFileInfo.
\endlist
- \sa close()
+ \sa close(), QT_USE_NODISCARD_FILE_OPEN
\b{Note for the Windows Platform}
@@ -1043,7 +1066,7 @@ bool QFile::open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
\warning Since this function opens the file without specifying the file name,
you cannot use this QFile with a QFileInfo.
- \sa close()
+ \sa close(), QT_USE_NODISCARD_FILE_OPEN
*/
bool QFile::open(int fd, OpenMode mode, FileHandleFlags handleFlags)
{
@@ -1250,6 +1273,107 @@ qint64 QFile::size() const
*/
+/*!
+ \class QNtfsPermissionCheckGuard
+ \since 6.6
+ \inmodule QtCore
+ \brief The QNtfsPermissionCheckGuard class is a RAII class to manage NTFS
+ permission checking.
+
+ \ingroup io
+
+ For performance reasons, QFile, QFileInfo, and related classes do not
+ perform full ownership and permission (ACL) checking on NTFS file systems
+ by default. During the lifetime of any instance of this class, that
+ default is overridden and advanced checking is performed. This provides
+ a safe and easy way to manage enabling and disabling this change to the
+ default behavior.
+
+ Example:
+
+ \snippet ntfsp.cpp raii
+
+ This class is available only on Windows.
+
+ \section1 qt_ntfs_permission_lookup
+
+ Prior to Qt 6.6, the user had to directly manipulate the global variable
+ \c qt_ntfs_permission_lookup. However, this was a non-atomic global
+ variable and as such it was prone to data races.
+
+ The variable \c qt_ntfs_permission_lookup is therefore deprecated since Qt
+ 6.6.
+*/
+
+/*!
+ \fn QNtfsPermissionCheckGuard::QNtfsPermissionCheckGuard()
+
+ Creates a guard and calls the function qEnableNtfsPermissionChecks().
+*/
+
+/*!
+ \fn QNtfsPermissionCheckGuard::~QNtfsPermissionCheckGuard()
+
+ Destroys the guard and calls the function qDisableNtfsPermissionChecks().
+*/
+
+
+/*!
+ \fn bool qEnableNtfsPermissionChecks()
+ \since 6.6
+ \threadsafe
+ \relates QNtfsPermissionCheckGuard
+
+ Enables permission checking on NTFS file systems. Returns \c true if the check
+ was already enabled before the call to this function, meaning that there
+ are other users.
+
+ This function is only available on Windows and makes the direct
+ manipulation of \l qt_ntfs_permission_lookup obsolete.
+
+ This is a low-level function, please consider the RAII class
+ \l QNtfsPermissionCheckGuard instead.
+
+ \note The thread-safety of this function holds only as long as there are no
+ concurrent updates to \l qt_ntfs_permission_lookup.
+*/
+
+/*!
+ \fn bool qDisableNtfsPermissionChecks()
+ \since 6.6
+ \threadsafe
+ \relates QNtfsPermissionCheckGuard
+
+ Disables permission checking on NTFS file systems. Returns \c true if the
+ check is disabled, meaning that there are no more users.
+
+ This function is only available on Windows and makes the direct
+ manipulation of \l qt_ntfs_permission_lookup obsolete.
+
+ This is a low-level function and must (only) be called to match one earlier
+ call to qEnableNtfsPermissionChecks(). Please consider the RAII class
+ \l QNtfsPermissionCheckGuard instead.
+
+ \note The thread-safety of this function holds only as long as there are no
+ concurrent updates to \l qt_ntfs_permission_lookup.
+*/
+
+/*!
+ \fn bool qAreNtfsPermissionChecksEnabled()
+ \since 6.6
+ \threadsafe
+ \relates QNtfsPermissionCheckGuard
+
+ Checks the status of the permission checks on NTFS file systems. Returns
+ \c true if the check is enabled.
+
+ This function is only available on Windows and makes the direct
+ manipulation of \l qt_ntfs_permission_lookup obsolete.
+
+ \note The thread-safety of this function holds only as long as there are no
+ concurrent updates to \l qt_ntfs_permission_lookup.
+*/
+
QT_END_NAMESPACE
#ifndef QT_NO_QOBJECT
diff --git a/src/corelib/io/qfile.h b/src/corelib/io/qfile.h
index cb06665a9f..058b2fa236 100644
--- a/src/corelib/io/qfile.h
+++ b/src/corelib/io/qfile.h
@@ -26,7 +26,7 @@ namespace std {
QT_BEGIN_NAMESPACE
-#ifdef Q_OS_WIN
+#if defined(Q_OS_WIN) || defined(Q_QDOC)
#if QT_DEPRECATED_SINCE(6,6)
QT_DEPRECATED_VERSION_X_6_6("Use QNtfsPermissionCheckGuard RAII class instead.")
@@ -37,10 +37,11 @@ Q_CORE_EXPORT bool qEnableNtfsPermissionChecks() noexcept;
Q_CORE_EXPORT bool qDisableNtfsPermissionChecks() noexcept;
Q_CORE_EXPORT bool qAreNtfsPermissionChecksEnabled() noexcept;
-class [[nodiscard]] QNtfsPermissionCheckGuard
+class QNtfsPermissionCheckGuard
{
Q_DISABLE_COPY_MOVE(QNtfsPermissionCheckGuard)
public:
+ Q_NODISCARD_CTOR
QNtfsPermissionCheckGuard()
{
qEnableNtfsPermissionChecks();
@@ -281,10 +282,10 @@ public:
}
#endif // QT_CONFIG(cxx17_filesystem)
- bool open(OpenMode flags) override;
- bool open(OpenMode flags, Permissions permissions);
- bool open(FILE *f, OpenMode ioFlags, FileHandleFlags handleFlags=DontCloseHandle);
- bool open(int fd, OpenMode ioFlags, FileHandleFlags handleFlags=DontCloseHandle);
+ QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override;
+ QFILE_MAYBE_NODISCARD bool open(OpenMode flags, Permissions permissions);
+ QFILE_MAYBE_NODISCARD bool open(FILE *f, OpenMode ioFlags, FileHandleFlags handleFlags=DontCloseHandle);
+ QFILE_MAYBE_NODISCARD bool open(int fd, OpenMode ioFlags, FileHandleFlags handleFlags=DontCloseHandle);
qint64 size() const override;
diff --git a/src/corelib/io/qfiledevice.cpp b/src/corelib/io/qfiledevice.cpp
index 092b09ae05..431dc65f5b 100644
--- a/src/corelib/io/qfiledevice.cpp
+++ b/src/corelib/io/qfiledevice.cpp
@@ -168,6 +168,35 @@ void QFileDevicePrivate::setError(QFileDevice::FileError err, int errNum)
handle is left open when the QFile object is destroyed.
*/
+/*!
+ \macro QT_USE_NODISCARD_FILE_OPEN
+ \macro QT_NO_USE_NODISCARD_FILE_OPEN
+ \relates QFileDevice
+ \since 6.8
+
+ File-related I/O classes (such as QFile, QSaveFile, QTemporaryFile)
+ have an \c{open()} method to open the file they act upon. It is
+ important to check the return value of the call to \c{open()}
+ before proceeding with reading or writing data into the file.
+
+ For this reason, starting with Qt 6.8, some overloads of \c{open()}
+ have been marked with the \c{[[nodiscard]]} attribute. Since this
+ change may raise warnings in existing codebases, user code can
+ opt-in or opt-out from having the attribute applied by defining
+ certain macros:
+
+ \list
+ \li If the \c{QT_USE_NODISCARD_FILE_OPEN} macro is defined,
+ overloads of \c{open()} are marked as \c{[[nodiscard]]}.
+ \li If the \c{QT_NO_USE_NODISCARD_FILE_OPEN} is defined, the
+ overloads of \c{open()} are \e{not} marked as \c{[[nodiscard]]}.
+ \li If neither macro is defined, then the default up to and
+ including Qt 6.9 is not to have the attribute. Starting from Qt 6.10,
+ the attribute is automatically applied.
+ \li If both macros are defined, the program is ill-formed.
+ \endlist
+*/
+
#ifdef QT_NO_QOBJECT
QFileDevice::QFileDevice()
: QIODevice(*new QFileDevicePrivate)
@@ -734,15 +763,6 @@ bool QFileDevice::unmap(uchar *address)
\sa setFileTime(), fileTime(), QFileInfo::fileTime()
*/
-static inline QAbstractFileEngine::FileTime FileDeviceTimeToAbstractFileEngineTime(QFileDevice::FileTime time)
-{
- static_assert(int(QFileDevice::FileAccessTime) == int(QAbstractFileEngine::AccessTime));
- static_assert(int(QFileDevice::FileBirthTime) == int(QAbstractFileEngine::BirthTime));
- static_assert(int(QFileDevice::FileMetadataChangeTime) == int(QAbstractFileEngine::MetadataChangeTime));
- static_assert(int(QFileDevice::FileModificationTime) == int(QAbstractFileEngine::ModificationTime));
- return QAbstractFileEngine::FileTime(time);
-}
-
/*!
\since 5.10
Returns the file time specified by \a time.
@@ -756,7 +776,7 @@ QDateTime QFileDevice::fileTime(QFileDevice::FileTime time) const
Q_D(const QFileDevice);
if (d->engine())
- return d->engine()->fileTime(FileDeviceTimeToAbstractFileEngineTime(time));
+ return d->engine()->fileTime(time);
return QDateTime();
}
@@ -779,7 +799,7 @@ bool QFileDevice::setFileTime(const QDateTime &newDate, QFileDevice::FileTime fi
return false;
}
- if (!d->fileEngine->setFileTime(newDate, FileDeviceTimeToAbstractFileEngineTime(fileTime))) {
+ if (!d->fileEngine->setFileTime(newDate, fileTime)) {
d->setError(d->fileEngine->error(), d->fileEngine->errorString());
return false;
}
diff --git a/src/corelib/io/qfiledevice.h b/src/corelib/io/qfiledevice.h
index 79788e2aaf..5274025715 100644
--- a/src/corelib/io/qfiledevice.h
+++ b/src/corelib/io/qfiledevice.h
@@ -12,6 +12,22 @@ QT_BEGIN_NAMESPACE
class QDateTime;
class QFileDevicePrivate;
+#if !defined(QT_USE_NODISCARD_FILE_OPEN) && !defined(QT_NO_USE_NODISCARD_FILE_OPEN)
+# if QT_VERSION < QT_VERSION_CHECK(6, 10, 0)
+# define QT_NO_USE_NODISCARD_FILE_OPEN
+# else
+# define QT_USE_NODISCARD_FILE_OPEN
+# endif
+#endif
+
+#if defined(QT_USE_NODISCARD_FILE_OPEN) && defined(QT_NO_USE_NODISCARD_FILE_OPEN)
+#error "Inconsistent macro definition for nodiscard QFile::open"
+#elif defined(QT_USE_NODISCARD_FILE_OPEN)
+#define QFILE_MAYBE_NODISCARD [[nodiscard]]
+#else /* QT_NO_USE_NODISCARD_FILE_OPEN */
+#define QFILE_MAYBE_NODISCARD
+#endif
+
class Q_CORE_EXPORT QFileDevice : public QIODevice
{
#ifndef QT_NO_QOBJECT
diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp
index aa1bde6a4a..6bc0128aff 100644
--- a/src/corelib/io/qfileinfo.cpp
+++ b/src/corelib/io/qfileinfo.cpp
@@ -157,7 +157,7 @@ uint QFileInfoPrivate::getFileFlags(QAbstractFileEngine::FileFlags request) cons
return fileFlags & request.toInt();
}
-QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) const
+QDateTime &QFileInfoPrivate::getFileTime(QFile::FileTime request) const
{
Q_ASSERT(fileEngine); // should never be called when using the native FS
if (!cache_enabled)
@@ -165,16 +165,16 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request)
uint cf = 0;
switch (request) {
- case QAbstractFileEngine::AccessTime:
+ case QFile::FileAccessTime:
cf = CachedATime;
break;
- case QAbstractFileEngine::BirthTime:
+ case QFile::FileBirthTime:
cf = CachedBTime;
break;
- case QAbstractFileEngine::MetadataChangeTime:
+ case QFile::FileMetadataChangeTime:
cf = CachedMCTime;
break;
- case QAbstractFileEngine::ModificationTime:
+ case QFile::FileModificationTime:
cf = CachedMTime;
break;
}
@@ -192,46 +192,53 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request)
\class QFileInfo
\inmodule QtCore
\reentrant
- \brief The QFileInfo class provides system-independent file information.
+ \brief The QFileInfo class provides an OS-independent API to retrieve
+ information about file system entries.
\ingroup io
\ingroup shared
- QFileInfo provides information about a file's name and position
- (path) in the file system, its access rights and whether it is a
- directory or symbolic link, etc. The file's size and last
- modified/read times are also available. QFileInfo can also be
- used to obtain information about a Qt \l{resource
- system}{resource}.
-
- A QFileInfo can point to a file with either a relative or an
- absolute file path. Absolute file paths begin with the directory
- separator "/" (or with a drive specification on Windows). Relative
- file names begin with a directory name or a file name and specify
- a path relative to the current working directory. An example of an
- absolute path is the string "/tmp/quartz". A relative path might
- look like "src/fatlib". You can use the function isRelative() to
- check whether a QFileInfo is using a relative or an absolute file
- path. You can call the function makeAbsolute() to convert a
- relative QFileInfo's path to an absolute path.
+ \compares equality
- \note Paths starting with a colon (\e{:}) are always considered
- absolute, as they denote a QResource.
+ QFileInfo provides information about a file system entry, such as its
+ name, path, access rights and whether it is a regular file, directory or
+ symbolic link. The entry's size and last modified/read times are also
+ available. QFileInfo can also be used to obtain information about a Qt
+ \l{resource system}{resource}.
+
+ A QFileInfo can point to a file system entry with either an absolute or
+ a relative path:
+ \list
+ \li \include qfileinfo.cpp absolute-path-unix-windows
- The file that the QFileInfo works on is set in the constructor or
- later with setFile(). Use exists() to see if the file exists and
- size() to get its size.
+ \li \include qfileinfo.cpp relative-path-note
+ \endlist
- The file's type is obtained with isFile(), isDir() and
- isSymLink(). The symLinkTarget() function provides the name of the file
- the symlink points to.
+ An example of an absolute path is the string \c {"/tmp/quartz"}. A relative
+ path may look like \c {"src/fatlib"}. You can use the function isRelative()
+ to check whether a QFileInfo is using a relative or an absolute path. You
+ can call the function makeAbsolute() to convert a relative QFileInfo's
+ path to an absolute path.
- Elements of the file's name can be extracted with path() and
- fileName(). The fileName()'s parts can be extracted with
- baseName(), suffix() or completeSuffix(). QFileInfo objects to
- directories created by Qt classes will not have a trailing file
- separator. If you wish to use trailing separators in your own file
- info objects, just append one to the file name given to the constructors
+//! [qresource-virtual-fs-colon]
+ \note Paths starting with a colon (\e{:}) are always considered
+ absolute, as they denote a QResource.
+//! [qresource-virtual-fs-colon]
+
+ The file system entry path that the QFileInfo works on is set in the
+ constructor or later with setFile(). Use exists() to see if the entry
+ actually exists and size() to get its size.
+
+ The file system entry's type is obtained with isFile(), isDir(), and
+ isSymLink(). The symLinkTarget() function provides the absolute path of
+ the target the symlink points to.
+
+ The path elements of the file system entry can be extracted with path()
+ and fileName(). The fileName()'s parts can be extracted with baseName(),
+ suffix(), or completeSuffix(). QFileInfo objects referring to directories
+ created by Qt classes will not have a trailing directory separator
+ \c{'/'}. If you wish to use trailing separators in your own file info
+ objects, just append one to the entry's path given to the constructors
or setFile().
Date and time related information are returned by birthTime(), fileTime(),
@@ -245,18 +252,18 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request)
\section1 Symbolic Links and Shortcuts
- On Unix (including \macos and iOS), the property getter functions in this
- class return the properties such as times and size of the target file, not
- the symlink, because Unix handles symlinks transparently. Opening a symlink
- using QFile effectively opens the link's target. For example:
+ On Unix (including \macos and iOS), the property getter functions in
+ this class return the properties such as times and size of the target,
+ not the symlink, because Unix handles symlinks transparently. Opening
+ a symlink using QFile effectively opens the link's target. For example:
\snippet code/src_corelib_io_qfileinfo.cpp 0
On Windows, shortcuts (\c .lnk files) are currently treated as symlinks. As
- on Unix systems, the property getters return the size of the targeted file,
- not the \c .lnk file itself. This behavior is deprecated and will likely be
- removed in a future version of Qt, after which \c .lnk files will be treated
- as regular files.
+ on Unix systems, the property getters return the size of the target,
+ not the \c .lnk file itself. This behavior is deprecated and will likely
+ be removed in a future version of Qt, after which \c .lnk files will be
+ treated as regular files.
\snippet code/src_corelib_io_qfileinfo.cpp 1
@@ -293,21 +300,24 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request)
\section1 Performance Considerations
- Some of QFileInfo's functions query the file system, but for
- performance reasons, some functions only operate on the
- file name itself. For example: To return the absolute path of
- a relative file name, absolutePath() has to query the file system.
- The path() function, however, can work on the file name directly,
- and so it is faster.
-
- To speed up performance, QFileInfo also caches information about
- the file. Because files can be changed by other users or programs, or
- even by other parts of the same program, there is a function that
- refreshes the file information: refresh(). If you want to switch
- off a QFileInfo's caching and force it to access the file system
- every time you request information from it call setCaching(false).
- If you want to make sure that all information is read from the
- file system, use stat().
+ Some of QFileInfo's functions have to query the file system, but for
+ performance reasons, some functions only operate on the path string.
+ For example: To return the absolute path of a relative entry's path,
+ absolutePath() has to query the file system. The path() function, however,
+ can work on the file name directly, and so it is faster.
+
+ QFileInfo also caches information about the file system entry it refers
+ to. Because the file system can be changed by other users or programs,
+ or even by other parts of the same program, there is a function that
+ refreshes the information stored in QFileInfo, namely refresh(). To switch
+ off a QFileInfo's caching (that is, force it to query the underlying file
+ system every time you request information from it), call setCaching(false).
+
+ Fetching information from the file system is typically done by calling
+ (possibly) expensive system functions, so QFileInfo (depending on the
+ implementation) might not fetch all the information from the file system
+ at construction. To make sure that all information is read from the file
+ system immediately, use the stat() member function.
\l{birthTime()}, \l{fileTime()}, \l{lastModified()}, \l{lastRead()},
and \l{metadataChangeTime()} return times in \e{local time} by default.
@@ -338,9 +348,8 @@ QFileInfo::QFileInfo(QFileInfoPrivate *p) : d_ptr(p)
}
/*!
- Constructs an empty QFileInfo object.
-
- Note that an empty QFileInfo object contain no file reference.
+ Constructs an empty QFileInfo object that doesn't refer to any file
+ system entry.
\sa setFile()
*/
@@ -349,12 +358,16 @@ QFileInfo::QFileInfo() : d_ptr(new QFileInfoPrivate())
}
/*!
- Constructs a new QFileInfo that gives information about the given
- file. The \a file can also include an absolute or relative path.
+ Constructs a QFileInfo that gives information about a file system entry
+ located at \a path that can be absolute or relative.
+
+//! [preserve-relative-path]
+ If \a path is relative, the QFileInfo will also have a relative path.
+//! [preserve-relative-path]
\sa setFile(), isRelative(), QDir::setCurrent(), QDir::isRelativePath()
*/
-QFileInfo::QFileInfo(const QString &file) : d_ptr(new QFileInfoPrivate(file))
+QFileInfo::QFileInfo(const QString &path) : d_ptr(new QFileInfoPrivate(path))
{
}
@@ -373,18 +386,20 @@ QFileInfo::QFileInfo(const QFileDevice &file) : d_ptr(new QFileInfoPrivate(file.
/*!
Constructs a new QFileInfo that gives information about the given
- \a file relative to the directory \a dir.
+ file system entry \a path that is relative to the directory \a dir.
+//! [preserve-relative-or-absolute]
If \a dir has a relative path, the QFileInfo will also have a
relative path.
- If \a file is an absolute path, then the directory specified
- by \a dir will be disregarded.
+ If \a path is absolute, then the directory specified by \a dir
+ will be disregarded.
+//! [preserve-relative-or-absolute]
\sa isRelative()
*/
-QFileInfo::QFileInfo(const QDir &dir, const QString &file)
- : d_ptr(new QFileInfoPrivate(dir.filePath(file)))
+QFileInfo::QFileInfo(const QDir &dir, const QString &path)
+ : d_ptr(new QFileInfoPrivate(dir.filePath(path)))
{
}
@@ -406,58 +421,57 @@ QFileInfo::~QFileInfo()
}
/*!
- \fn bool QFileInfo::operator!=(const QFileInfo &fileinfo) const
+ \fn bool QFileInfo::operator!=(const QFileInfo &lhs, const QFileInfo &rhs)
- Returns \c true if this QFileInfo object refers to a different file
- than the one specified by \a fileinfo; otherwise returns \c false.
+ Returns \c true if QFileInfo \a lhs refers to a different file system
+ entry than the one referred to by \a rhs; otherwise returns \c false.
\sa operator==()
*/
/*!
- Returns \c true if this QFileInfo object refers to a file in the same
- location as \a fileinfo; otherwise returns \c false.
+ \fn bool QFileInfo::operator==(const QFileInfo &lhs, const QFileInfo &rhs)
+
+ Returns \c true if QFileInfo \a lhs and QFileInfo \a rhs refer to the same
+ entry on the file system; otherwise returns \c false.
- Note that the result of comparing two empty QFileInfo objects,
- containing no file references (file paths that do not exist or
- are empty), is undefined.
+ Note that the result of comparing two empty QFileInfo objects, containing
+ no file system entry references (paths that do not exist or are empty),
+ is undefined.
- \warning This will not compare two different symbolic links
- pointing to the same file.
+ \warning This will not compare two different symbolic links pointing to
+ the same target.
- \warning Long and short file names that refer to the same file on Windows
- are treated as if they referred to different files.
+ \warning On Windows, long and short paths that refer to the same file
+ system entry are treated as if they referred to different entries.
\sa operator!=()
*/
-bool QFileInfo::operator==(const QFileInfo &fileinfo) const
+bool comparesEqual(const QFileInfo &lhs, const QFileInfo &rhs)
{
- Q_D(const QFileInfo);
- // ### Qt 5: understand long and short file names on Windows
- // ### (GetFullPathName()).
- if (fileinfo.d_ptr == d_ptr)
+ if (rhs.d_ptr == lhs.d_ptr)
return true;
- if (d->isDefaultConstructed || fileinfo.d_ptr->isDefaultConstructed)
+ if (lhs.d_ptr->isDefaultConstructed || rhs.d_ptr->isDefaultConstructed)
return false;
// Assume files are the same if path is the same
- if (d->fileEntry.filePath() == fileinfo.d_ptr->fileEntry.filePath())
+ if (lhs.d_ptr->fileEntry.filePath() == rhs.d_ptr->fileEntry.filePath())
return true;
Qt::CaseSensitivity sensitive;
- if (d->fileEngine == nullptr || fileinfo.d_ptr->fileEngine == nullptr) {
- if (d->fileEngine != fileinfo.d_ptr->fileEngine) // one is native, the other is a custom file-engine
+ if (lhs.d_ptr->fileEngine == nullptr || rhs.d_ptr->fileEngine == nullptr) {
+ 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;
} else {
- if (d->fileEngine->caseSensitive() != fileinfo.d_ptr->fileEngine->caseSensitive())
+ if (lhs.d_ptr->fileEngine->caseSensitive() != rhs.d_ptr->fileEngine->caseSensitive())
return false;
- sensitive = d->fileEngine->caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
+ sensitive = lhs.d_ptr->fileEngine->caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
}
// Fallback to expensive canonical path computation
- return canonicalFilePath().compare(fileinfo.canonicalFilePath(), sensitive) == 0;
+ return lhs.canonicalFilePath().compare(rhs.canonicalFilePath(), sensitive) == 0;
}
/*!
@@ -478,24 +492,30 @@ QFileInfo &QFileInfo::operator=(const QFileInfo &fileinfo)
*/
/*!
- Sets the file that the QFileInfo provides information about to \a
- file.
+ Sets the path of the file system entry that this QFileInfo provides
+ information about to \a path that can be absolute or relative.
+
+//! [absolute-path-unix-windows]
+ On Unix, absolute paths begin with the directory separator \c {'/'}.
+ On Windows, absolute paths begin with a drive specification (for example,
+ \c {D:/}).
+//! [ absolute-path-unix-windows]
- The \a file can also include an absolute or relative file path.
- Absolute paths begin with the directory separator (e.g. "/" under
- Unix) or a drive specification (under Windows). Relative file
- names begin with a directory name or a file name and specify a
- path relative to the current directory.
+//! [relative-path-note]
+ Relative paths begin with a directory name or a regular file name and
+ specify a file system entry's path relative to the current working
+ directory.
+//! [relative-path-note]
Example:
\snippet code/src_corelib_io_qfileinfo.cpp 2
\sa isRelative(), QDir::setCurrent(), QDir::isRelativePath()
*/
-void QFileInfo::setFile(const QString &file)
+void QFileInfo::setFile(const QString &path)
{
bool caching = d_ptr.constData()->cache_enabled;
- *this = QFileInfo(file);
+ *this = QFileInfo(path);
d_ptr->cache_enabled = caching;
}
@@ -518,27 +538,29 @@ void QFileInfo::setFile(const QFileDevice &file)
/*!
\overload
- Sets the file that the QFileInfo provides information about to \a
- file in directory \a dir.
+ Sets the path of the file system entry that this QFileInfo provides
+ information about to \a path in directory \a dir.
- If \a file includes a relative path, the QFileInfo will also
- have a relative path.
+ \include qfileinfo.cpp preserve-relative-or-absolute
\sa isRelative()
*/
-void QFileInfo::setFile(const QDir &dir, const QString &file)
+void QFileInfo::setFile(const QDir &dir, const QString &path)
{
- setFile(dir.filePath(file));
+ setFile(dir.filePath(path));
}
/*!
- Returns an absolute path including the file name.
+ Returns the absolute full path to the file system entry this QFileInfo
+ refers to, including the entry's name.
+
+ \include qfileinfo.cpp absolute-path-unix-windows
+
+//! [windows-network-shares]
+ On Windows, the paths of network shares that are not mapped to a drive
+ letter begin with \c{//sharename/}.
+//! [windows-network-shares]
- The absolute path name consists of the full path and the file
- name. On Unix this will always begin with the root, '/',
- directory. On Windows this will always begin 'D:/' where D is a
- drive letter, except for network shares that are not mapped to a
- drive letter, in which case the path will begin '//sharename/'.
QFileInfo will uppercase drive letters. Note that QDir does not do
this. The code snippet below shows this.
@@ -562,10 +584,11 @@ QString QFileInfo::absoluteFilePath() const
}
/*!
- Returns the canonical path including the file name, i.e. an absolute
- path without symbolic links or redundant "." or ".." elements.
+ Returns the file system entry's canonical path, including the entry's
+ name, that is, an absolute path without symbolic links or redundant
+ \c{'.'} or \c{'..'} elements.
- If the file does not exist, canonicalFilePath() returns an empty
+ If the entry does not exist, canonicalFilePath() returns an empty
string.
\sa filePath(), absoluteFilePath(), dir()
@@ -580,13 +603,12 @@ QString QFileInfo::canonicalFilePath() const
/*!
- Returns a file's path absolute path. This doesn't include the
- file name.
+ Returns the absolute path of the file system entry this QFileInfo refers to,
+ excluding the entry's name.
+
+ \include qfileinfo.cpp absolute-path-unix-windows
- On Unix the absolute path will always begin with the root, '/',
- directory. On Windows this will always begin 'D:/' where D is a
- drive letter, except for network shares that are not mapped to a
- drive letter, in which case the path will begin '//sharename/'.
+ \include qfileinfo.cpp windows-network-shares
In contrast to canonicalPath() symbolic links or redundant "." or
".." elements are not necessarily removed.
@@ -606,10 +628,10 @@ QString QFileInfo::absolutePath() const
}
/*!
- Returns the file's path canonical path (excluding the file name),
+ Returns the file system entry's canonical path (excluding the entry's name),
i.e. an absolute path without symbolic links or redundant "." or ".." elements.
- If the file does not exist, canonicalPath() returns an empty string.
+ If the entry does not exist, this method returns an empty string.
\sa path(), absolutePath()
*/
@@ -622,11 +644,11 @@ QString QFileInfo::canonicalPath() const
}
/*!
- Returns the file's path. This doesn't include the file name.
+ Returns the path of the file system entry this QFileInfo refers to,
+ excluding the entry's name.
- Note that, if this QFileInfo object is given a path ending in a
- slash, the name of the file is considered empty and this function
- will return the entire path.
+ \include qfileinfo.cpp path-ends-with-slash-empty-name-component
+ In this case, this function will return the entire path.
\sa filePath(), absolutePath(), canonicalPath(), dir(), fileName(), isRelative()
*/
@@ -641,22 +663,21 @@ QString QFileInfo::path() const
/*!
\fn bool QFileInfo::isAbsolute() const
- Returns \c true if the file path is absolute, otherwise returns \c false (i.e.
- the path is relative).
+ Returns \c true if the file system entry's path is absolute, otherwise
+ returns \c false (that is, the path is relative).
- \note Paths starting with a colon (\e{:}) are always considered absolute, as
- they denote a QResource.
+ \include qfileinfo.cpp qresource-virtual-fs-colon
\sa isRelative()
*/
/*!
- Returns \c true if the file path is relative, otherwise returns \c
- false (i.e. the path is absolute). (E.g. under Unix a path is absolute
- if it begins with a "/").
+ Returns \c true if the file system entry's path is relative, otherwise
+ returns \c false (that is, the path is absolute).
+
+ \include qfileinfo.cpp absolute-path-unix-windows
- \note Paths starting with a colon (\e{:}) are always considered absolute,
- as they denote a QResource.
+ \include qfileinfo.cpp qresource-virtual-fs-colon
\sa isAbsolute()
*/
@@ -671,9 +692,9 @@ bool QFileInfo::isRelative() const
}
/*!
- Converts the file's path to an absolute path if it is not already in that form.
- Returns \c true to indicate that the path was converted; otherwise returns \c false
- to indicate that the path was already absolute.
+ If the file system entry's path is relative, this method converts it to
+ an absolute path and returns \c true; if the path is already absolute,
+ this method returns \c false.
\sa filePath(), isRelative()
*/
@@ -688,10 +709,11 @@ bool QFileInfo::makeAbsolute()
}
/*!
- Returns \c true if the file exists; otherwise returns \c false.
+ Returns \c true if the file system entry this QFileInfo refers to exists;
+ otherwise returns \c false.
- \note If the file is a symlink that points to a non-existing
- file, false is returned.
+ \note If the entry is a symlink that points to a non-existing
+ target, this method returns \c false.
*/
bool QFileInfo::exists() const
{
@@ -709,24 +731,23 @@ bool QFileInfo::exists() const
/*!
\since 5.2
- Returns \c true if the \a file exists; otherwise returns \c false.
+ Returns \c true if the file system entry \a path exists; otherwise
+ returns \c false.
- \note If \a file is a symlink that points to a non-existing
- file, false is returned.
+ \note If \a path is a symlink that points to a non-existing
+ target, this method returns \c false.
\note Using this function is faster than using
- \c QFileInfo(file).exists() for file system access.
+ \c QFileInfo(path).exists() for file system access.
*/
-bool QFileInfo::exists(const QString &file)
+bool QFileInfo::exists(const QString &path)
{
- if (file.isEmpty())
+ if (path.isEmpty())
return false;
- QFileSystemEntry entry(file);
+ QFileSystemEntry entry(path);
QFileSystemMetaData data;
- std::unique_ptr<QAbstractFileEngine> engine
- {QFileSystemEngine::resolveEntryAndCreateLegacyEngine(entry, data)};
// Expensive fallback to non-QFileSystemEngine implementation
- if (engine)
+ if (auto engine = QFileSystemEngine::createLegacyEngine(entry, data))
return QFileInfo(new QFileInfoPrivate(entry, data, std::move(engine))).exists();
QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute);
@@ -734,8 +755,9 @@ bool QFileInfo::exists(const QString &file)
}
/*!
- Refreshes the information about the file, i.e. reads in information
- from the file system the next time a cached property is fetched.
+ Refreshes the information about the file system entry this QFileInfo
+ refers to, that is, reads in information from the file system the next
+ time a cached property is fetched.
*/
void QFileInfo::refresh()
{
@@ -744,8 +766,8 @@ void QFileInfo::refresh()
}
/*!
- Returns the file name, including the path (which may be absolute
- or relative).
+ Returns the path of the file system entry this QFileInfo refers to;
+ the path may be absolute or relative.
\sa absoluteFilePath(), canonicalFilePath(), isRelative()
*/
@@ -758,13 +780,16 @@ QString QFileInfo::filePath() const
}
/*!
- Returns the name of the file, excluding the path.
+ Returns the name of the file system entry this QFileInfo refers to,
+ excluding the path.
Example:
\snippet code/src_corelib_io_qfileinfo.cpp 3
- Note that, if this QFileInfo object is given a path ending in a
- slash, the name of the file is considered empty.
+//! [path-ends-with-slash-empty-name-component]
+ \note If this QFileInfo is given a path ending with a directory separator
+ \c{'/'}, the entry's name part is considered empty.
+//! [path-ends-with-slash-empty-name-component]
\sa isRelative(), filePath(), baseName(), suffix()
*/
@@ -890,9 +915,10 @@ QString QFileInfo::suffix() const
/*!
- Returns the path of the object's parent directory as a QDir object.
+ Returns a QDir object representing the path of the parent directory of the
+ file system entry that this QFileInfo refers to.
- \b{Note:} The QDir returned always corresponds to the object's
+ \note The QDir returned always corresponds to the object's
parent directory, even if the QFileInfo represents a directory.
For each of the following, dir() returns the QDir
@@ -914,7 +940,10 @@ QDir QFileInfo::dir() const
}
/*!
- Returns the file's absolute path as a QDir object.
+ Returns a QDir object representing the absolute path of the parent
+ directory of the file system entry that this QFileInfo refers to.
+
+ \snippet code/src_corelib_io_qfileinfo.cpp 11
\sa dir(), filePath(), fileName(), isRelative()
*/
@@ -924,13 +953,13 @@ QDir QFileInfo::absoluteDir() const
}
/*!
- Returns \c true if the user can read the file; otherwise returns \c false.
+ Returns \c true if the user can read the file system entry this QFileInfo
+ refers to; otherwise returns \c false.
- If the file is a symlink, this function returns true if the target is
- readable (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\note If the \l{NTFS permissions} check has not been enabled, the result
- on Windows will merely reflect whether the file exists.
+ on Windows will merely reflect whether the entry exists.
\sa isWritable(), isExecutable(), permission()
*/
@@ -939,18 +968,18 @@ bool QFileInfo::isReadable() const
Q_D(const QFileInfo);
return d->checkAttribute<bool>(
QFileSystemMetaData::UserReadPermission,
- [d]() { return (d->metaData.permissions() & QFile::ReadUser) != 0; },
+ [d]() { return d->metaData.isReadable(); },
[d]() { return d->getFileFlags(QAbstractFileEngine::ReadUserPerm); });
}
/*!
- Returns \c true if the user can write to the file; otherwise returns \c false.
+ Returns \c true if the user can write to the file system entry this
+ QFileInfo refers to; otherwise returns \c false.
- If the file is a symlink, this function returns true if the target is
- writeable (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\note If the \l{NTFS permissions} check has not been enabled, the result on
- Windows will merely reflect whether the file is marked as Read Only.
+ Windows will merely reflect whether the entry is marked as Read Only.
\sa isReadable(), isExecutable(), permission()
*/
@@ -959,15 +988,18 @@ bool QFileInfo::isWritable() const
Q_D(const QFileInfo);
return d->checkAttribute<bool>(
QFileSystemMetaData::UserWritePermission,
- [d]() { return (d->metaData.permissions() & QFile::WriteUser) != 0; },
+ [d]() { return d->metaData.isWritable(); },
[d]() { return d->getFileFlags(QAbstractFileEngine::WriteUserPerm); });
}
/*!
- Returns \c true if the file is executable; otherwise returns \c false.
+ Returns \c true if the file system entry this QFileInfo refers to is
+ executable; otherwise returns \c false.
- If the file is a symlink, this function returns true if the target is
- executable (not the symlink).
+//! [info-about-target-not-symlink]
+ If the file is a symlink, this function returns information about the
+ target, not the symlink.
+//! [info-about-target-not-symlink]
\sa isReadable(), isWritable(), permission()
*/
@@ -976,15 +1008,16 @@ bool QFileInfo::isExecutable() const
Q_D(const QFileInfo);
return d->checkAttribute<bool>(
QFileSystemMetaData::UserExecutePermission,
- [d]() { return (d->metaData.permissions() & QFile::ExeUser) != 0; },
+ [d]() { return d->metaData.isExecutable(); },
[d]() { return d->getFileFlags(QAbstractFileEngine::ExeUserPerm); });
}
/*!
- Returns \c true if this is a `hidden' file; otherwise returns \c false.
+ Returns \c true if the file system entry this QFileInfo refers to is
+ `hidden'; otherwise returns \c false.
\b{Note:} This function returns \c true for the special entries "." and
- ".." on Unix, even though QDir::entryList threats them as shown. And note
+ ".." on Unix, even though QDir::entryList treats them as shown. And note
that, since this function inspects the file name, on Unix it will inspect
the name of the symlink, if this file is a symlink, not the target's name.
@@ -1029,8 +1062,7 @@ bool QFileInfo::isNativePath() const
object points to something that is not a file (such as a directory)
or that does not exist.
- If the file is a symlink, this function returns true if the target is a
- regular file (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa isDir(), isSymLink(), isBundle()
*/
@@ -1049,8 +1081,7 @@ bool QFileInfo::isFile() const
object points to something that is not a directory (such as a file)
or that does not exist.
- If the file is a symlink, this function returns true if the target is a
- directory (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa isFile(), isSymLink(), isBundle()
*/
@@ -1069,8 +1100,7 @@ bool QFileInfo::isDir() const
Returns \c true if this object points to a bundle or to a symbolic
link to a bundle on \macos and iOS; otherwise returns \c false.
- If the file is a symlink, this function returns true if the target is a
- bundle (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa isDir(), isSymLink(), isFile()
*/
@@ -1101,8 +1131,10 @@ bool QFileInfo::isBundle() const
\snippet code/src_corelib_io_qfileinfo.cpp 9
- \note If the symlink points to a non existing file, exists() returns
- false.
+//! [symlink-target-exists-behavior]
+ \note exists() returns \c true if the symlink points to an existing
+ target, otherwise it returns \c false.
+//! [symlink-target-exists-behavior]
\sa isFile(), isDir(), symLinkTarget()
*/
@@ -1130,8 +1162,7 @@ bool QFileInfo::isSymLink() const
(\c *.lnk files) on Windows and aliases on \macos. Use QFileInfo::isShortcut()
and QFileInfo::isAlias() instead.
- \note If the symlink points to a non existing file, exists() returns
- false.
+ \include qfileinfo.cpp symlink-target-exists-behavior
\sa isFile(), isDir(), isShortcut(), symLinkTarget()
*/
@@ -1250,8 +1281,8 @@ bool QFileInfo::isRoot() const
link.
This name may not represent an existing file; it is only a string.
- QFileInfo::exists() returns \c true if the symlink points to an
- existing file.
+
+ \include qfileinfo.cpp symlink-target-exists-behavior
\sa exists(), isSymLink(), isDir(), isFile()
*/
@@ -1313,8 +1344,7 @@ QString QFileInfo::junctionTarget() const
milliseconds). On Windows, it will return an empty string unless
the \l{NTFS permissions} check has been enabled.
- If the file is a symlink, this function returns the owner of the target
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa ownerId(), group(), groupId()
*/
@@ -1332,8 +1362,7 @@ QString QFileInfo::owner() const
On Windows and on systems where files do not have owners this
function returns ((uint) -2).
- If the file is a symlink, this function returns the id of the owner of the target
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa owner(), group(), groupId()
*/
@@ -1354,8 +1383,7 @@ uint QFileInfo::ownerId() const
This function can be time consuming under Unix (in the order of
milliseconds).
- If the file is a symlink, this function returns the owning group of the
- target (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa groupId(), owner(), ownerId()
*/
@@ -1373,8 +1401,7 @@ QString QFileInfo::group() const
On Windows and on systems where files do not have groups this
function always returns (uint) -2.
- If the file is a symlink, this function returns the id of the group owning the
- target (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa group(), owner(), ownerId()
*/
@@ -1401,8 +1428,7 @@ uint QFileInfo::groupId() const
Example:
\snippet code/src_corelib_io_qfileinfo.cpp 10
- If the file is a symlink, this function checks the permissions of the
- target (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa isReadable(), isWritable(), isExecutable()
*/
@@ -1427,8 +1453,7 @@ bool QFileInfo::permission(QFile::Permissions permissions) const
\note The result might be inaccurate on Windows if the
\l{NTFS permissions} check has not been enabled.
- If the file is a symlink, this function returns the permissions of the
- target (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
*/
QFile::Permissions QFileInfo::permissions() const
{
@@ -1446,8 +1471,7 @@ QFile::Permissions QFileInfo::permissions() const
Returns the file size in bytes. If the file does not exist or cannot be
fetched, 0 is returned.
- If the file is a symlink, the size of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\sa exists()
*/
@@ -1473,7 +1497,7 @@ qint64 QFileInfo::size() const
If the file birth time is not available, this function returns an invalid QDateTime.
- If the file is a symlink, the time of the target file is returned (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
This function overloads QFileInfo::birthTime(const QTimeZone &tz), and
returns the same as \c{birthTime(QTimeZone::LocalTime)}.
@@ -1492,11 +1516,12 @@ qint64 QFileInfo::size() const
If the file birth time is not available, this function returns an invalid
QDateTime.
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\since 6.6
- \sa lastModified(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime, const QTimeZone &)
+ \sa lastModified(const QTimeZone &), lastRead(const QTimeZone &),
+ metadataChangeTime(const QTimeZone &),
+ fileTime(QFileDevice::FileTime, const QTimeZone &)
*/
/*!
@@ -1509,8 +1534,7 @@ qint64 QFileInfo::size() const
occurs whenever the user writes or sets inode information (for example,
changing the file permissions).
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
This function overloads QFileInfo::metadataChangeTime(const QTimeZone &tz),
and returns the same as \c{metadataChangeTime(QTimeZone::LocalTime)}.
@@ -1529,11 +1553,12 @@ qint64 QFileInfo::size() const
\include qfileinfo.cpp file-times-in-time-zone
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\since 6.6
- \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime time, const QTimeZone &)
+ \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &),
+ lastRead(const QTimeZone &),
+ fileTime(QFileDevice::FileTime time, const QTimeZone &)
*/
/*!
@@ -1541,8 +1566,7 @@ qint64 QFileInfo::size() const
Returns the date and time when the file was last modified.
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
This function overloads \l{QFileInfo::lastModified(const QTimeZone &)},
and returns the same as \c{lastModified(QTimeZone::LocalTime)}.
@@ -1557,11 +1581,12 @@ qint64 QFileInfo::size() const
\include qfileinfo.cpp file-times-in-time-zone
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\since 6.6
- \sa birthTime(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime, const QTimeZone &)
+ \sa birthTime(const QTimeZone &), lastRead(const QTimeZone &),
+ metadataChangeTime(const QTimeZone &),
+ fileTime(QFileDevice::FileTime, const QTimeZone &)
*/
/*!
@@ -1572,8 +1597,7 @@ qint64 QFileInfo::size() const
On platforms where this information is not available, returns the same
time as lastModified().
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
This function overloads \l{QFileInfo::lastRead(const QTimeZone &)},
and returns the same as \c{lastRead(QTimeZone::LocalTime)}.
@@ -1591,11 +1615,12 @@ qint64 QFileInfo::size() const
On platforms where this information is not available, returns the same
time as lastModified().
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\since 6.6
- \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime, const QTimeZone &)
+ \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &),
+ metadataChangeTime(const QTimeZone &),
+ fileTime(QFileDevice::FileTime, const QTimeZone &)
*/
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && !defined(QT_BOOTSTRAPPED)
@@ -1604,11 +1629,10 @@ qint64 QFileInfo::size() const
If the time cannot be determined, an invalid date time is returned.
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
This function overloads
- \l{QFileInfo::fileTime(QFile::FileTime, const QTimeZone &)},
+ \l{QFileInfo::fileTime(QFileDevice::FileTime, const QTimeZone &)},
and returns the same as \c{fileTime(time, QTimeZone::LocalTime)}.
\since 5.10
@@ -1632,21 +1656,16 @@ QDateTime QFileInfo::fileTime(QFile::FileTime time) const {
If the time cannot be determined, an invalid date time is returned.
- If the file is a symlink, the time of the target file is returned
- (not the symlink).
+ \include qfileinfo.cpp info-about-target-not-symlink
\since 6.6
- \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), QDateTime::isValid()
+ \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &),
+ lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &),
+ QDateTime::isValid()
*/
QDateTime QFileInfo::fileTime(QFile::FileTime time, const QTimeZone &tz) const
{
- static_assert(int(QFile::FileAccessTime) == int(QAbstractFileEngine::AccessTime));
- static_assert(int(QFile::FileBirthTime) == int(QAbstractFileEngine::BirthTime));
- static_assert(int(QFile::FileMetadataChangeTime) == int(QAbstractFileEngine::MetadataChangeTime));
- static_assert(int(QFile::FileModificationTime) == int(QAbstractFileEngine::ModificationTime));
-
Q_D(const QFileInfo);
- auto fetime = QAbstractFileEngine::FileTime(time);
QFileSystemMetaData::MetaDataFlags flag;
switch (time) {
case QFile::FileAccessTime:
@@ -1663,9 +1682,10 @@ QDateTime QFileInfo::fileTime(QFile::FileTime time, const QTimeZone &tz) const
break;
}
- auto fsLambda = [d, fetime]() { return d->metaData.fileTime(fetime); };
- auto engineLambda = [d, fetime]() { return d->getFileTime(fetime); };
- const QDateTime dt = d->checkAttribute<QDateTime>(flag, fsLambda, engineLambda);
+ auto fsLambda = [d, time]() { return d->metaData.fileTime(time); };
+ auto engineLambda = [d, time]() { return d->getFileTime(time); };
+ const auto dt =
+ d->checkAttribute<QDateTime>(flag, std::move(fsLambda), std::move(engineLambda));
return dt.toTimeZone(tz);
}
@@ -1750,24 +1770,22 @@ QDebug operator<<(QDebug dbg, const QFileInfo &fi)
\sa setFile(), isRelative(), QDir::setCurrent(), QDir::isRelativePath()
*/
/*!
- \fn QFileInfo::QFileInfo(const QDir &dir, const std::filesystem::path &file)
+ \fn QFileInfo::QFileInfo(const QDir &dir, const std::filesystem::path &path)
\since 6.0
- Constructs a new QFileInfo that gives information about the given
- \a file relative to the directory \a dir.
+ Constructs a new QFileInfo that gives information about the file system
+ entry at \a path that is relative to the directory \a dir.
- If \a dir has a relative path, the QFileInfo will also have a
- relative path.
-
- If \a file is an absolute path, then the directory specified
- by \a dir will be disregarded.
+ \include qfileinfo.cpp preserve-relative-or-absolute
*/
/*!
- \fn void QFileInfo::setFile(const std::filesystem::path &file)
+ \fn void QFileInfo::setFile(const std::filesystem::path &path)
\since 6.0
- Sets the file that the QFileInfo provides information about to \a
- file.
+ Sets the path of file system entry that this QFileInfo provides
+ information about to \a path.
+
+ \include qfileinfo.cpp preserve-relative-path
*/
/*!
\fn std::filesystem::path QFileInfo::filesystemFilePath() const
@@ -1819,6 +1837,13 @@ QDebug operator<<(QDebug dbg, const QFileInfo &fi)
\sa symLinkTarget()
*/
/*!
+ \fn std::filesystem::path QFileInfo::filesystemReadSymLink() const
+ \since 6.6
+
+ Returns readSymLink() as a \c{std::filesystem::path}.
+ \sa readSymLink()
+*/
+/*!
\fn std::filesystem::path QFileInfo::filesystemJunctionTarget() const
\since 6.2
diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h
index 435fba5497..72c8519446 100644
--- a/src/corelib/io/qfileinfo.h
+++ b/src/corelib/io/qfileinfo.h
@@ -4,6 +4,7 @@
#ifndef QFILEINFO_H
#define QFILEINFO_H
+#include <QtCore/qcompare.h>
#include <QtCore/qfile.h>
#include <QtCore/qlist.h>
#include <QtCore/qshareddata.h>
@@ -21,6 +22,7 @@ class QFileInfoPrivate;
class Q_CORE_EXPORT QFileInfo
{
friend class QDirIteratorPrivate;
+ friend class QDirListingPrivate;
public:
explicit QFileInfo(QFileInfoPrivate *d);
@@ -58,8 +60,10 @@ public:
void swap(QFileInfo &other) noexcept
{ d_ptr.swap(other.d_ptr); }
+#if QT_CORE_REMOVED_SINCE(6, 8)
bool operator==(const QFileInfo &fileinfo) const;
inline bool operator!=(const QFileInfo &fileinfo) const { return !(operator==(fileinfo)); }
+#endif
void setFile(const QString &file);
void setFile(const QFileDevice &file);
@@ -151,7 +155,6 @@ public:
qint64 size() const;
-#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && !defined(QT_BOOTSTRAPPED)
QDateTime birthTime() const { return fileTime(QFile::FileBirthTime); }
QDateTime metadataChangeTime() const { return fileTime(QFile::FileMetadataChangeTime); }
QDateTime lastModified() const { return fileTime(QFile::FileModificationTime); }
@@ -163,25 +166,6 @@ public:
QDateTime lastModified(const QTimeZone &tz) const { return fileTime(QFile::FileModificationTime, tz); }
QDateTime lastRead(const QTimeZone &tz) const { return fileTime(QFile::FileAccessTime, tz); }
QDateTime fileTime(QFile::FileTime time, const QTimeZone &tz) const;
-#else
- QDateTime birthTime(const QTimeZone &tz = QTimeZone::LocalTime) const
- {
- return fileTime(QFile::FileBirthTime, tz);
- }
- QDateTime metadataChangeTime(const QTimeZone &tz = QTimeZone::LocalTime) const
- {
- return fileTime(QFile::FileMetadataChangeTime, tz);
- }
- QDateTime lastModified(const QTimeZone &tz = QTimeZone::LocalTime) const
- {
- return fileTime(QFile::FileModificationTime, tz);
- }
- QDateTime lastRead(const QTimeZone &tz = QTimeZone::LocalTime) const
- {
- return fileTime(QFile::FileAccessTime, tz);
- }
- QDateTime fileTime(QFile::FileTime time, const QTimeZone &tz = QTimeZone::LocalTime) const;
-#endif
bool caching() const;
void setCaching(bool on);
@@ -191,6 +175,8 @@ protected:
QSharedDataPointer<QFileInfoPrivate> d_ptr;
private:
+ friend Q_CORE_EXPORT bool comparesEqual(const QFileInfo &lhs, const QFileInfo &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QFileInfo)
QFileInfoPrivate* d_func();
inline const QFileInfoPrivate* d_func() const
{
diff --git a/src/corelib/io/qfileinfo_p.h b/src/corelib/io/qfileinfo_p.h
index 4da7c60792..4091a7464a 100644
--- a/src/corelib/io/qfileinfo_p.h
+++ b/src/corelib/io/qfileinfo_p.h
@@ -55,7 +55,7 @@ public:
: QSharedData(copy),
fileEntry(copy.fileEntry),
metaData(copy.metaData),
- fileEngine(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(fileEntry, metaData)),
+ fileEngine(QFileSystemEngine::createLegacyEngine(fileEntry, metaData)),
cachedFlags(0),
#ifndef QT_NO_FSFILEENGINE
isDefaultConstructed(false),
@@ -66,7 +66,7 @@ public:
{}
inline QFileInfoPrivate(const QString &file)
: fileEntry(file),
- fileEngine(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(fileEntry, metaData)),
+ fileEngine(QFileSystemEngine::createLegacyEngine(fileEntry, metaData)),
cachedFlags(0),
#ifndef QT_NO_FSFILEENGINE
isDefaultConstructed(file.isEmpty()),
@@ -81,7 +81,7 @@ public:
: QSharedData(),
fileEntry(file),
metaData(data),
- fileEngine(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(fileEntry, metaData)),
+ fileEngine(QFileSystemEngine::createLegacyEngine(fileEntry, metaData)),
cachedFlags(0),
isDefaultConstructed(false),
cache_enabled(true), fileFlags(0), fileSize(0)
@@ -122,7 +122,7 @@ public:
}
uint getFileFlags(QAbstractFileEngine::FileFlags) const;
- QDateTime &getFileTime(QAbstractFileEngine::FileTime) const;
+ QDateTime &getFileTime(QFile::FileTime) const;
QString getFileName(QAbstractFileEngine::FileName) const;
QString getFileOwner(QAbstractFileEngine::FileOwner own) const;
@@ -133,7 +133,7 @@ public:
mutable QString fileNames[QAbstractFileEngine::NFileNames];
mutable QString fileOwners[2]; // QAbstractFileEngine::FileOwner: OwnerUser and OwnerGroup
- mutable QDateTime fileTimes[4]; // QAbstractFileEngine::FileTime: BirthTime, MetadataChangeTime, ModificationTime, AccessTime
+ mutable QDateTime fileTimes[4]; // QFile::FileTime: FileBirthTime, FileMetadataChangeTime, FileModificationTime, FileAccessTime
mutable uint cachedFlags : 30;
bool const isDefaultConstructed : 1; // QFileInfo is a default constructed instance
@@ -146,8 +146,8 @@ public:
{ if (cache_enabled) cachedFlags |= c; }
template <typename Ret, typename FSLambda, typename EngineLambda>
- Ret checkAttribute(Ret defaultValue, QFileSystemMetaData::MetaDataFlags fsFlags, const FSLambda &fsLambda,
- const EngineLambda &engineLambda) const
+ Ret checkAttribute(Ret defaultValue, QFileSystemMetaData::MetaDataFlags fsFlags,
+ FSLambda fsLambda, EngineLambda engineLambda) const
{
if (isDefaultConstructed)
return defaultValue;
@@ -161,10 +161,10 @@ public:
}
template <typename Ret, typename FSLambda, typename EngineLambda>
- Ret checkAttribute(QFileSystemMetaData::MetaDataFlags fsFlags, const FSLambda &fsLambda,
- const EngineLambda &engineLambda) const
+ Ret checkAttribute(QFileSystemMetaData::MetaDataFlags fsFlags, FSLambda fsLambda,
+ EngineLambda engineLambda) const
{
- return checkAttribute(Ret(), fsFlags, fsLambda, engineLambda);
+ return checkAttribute(Ret(), std::move(fsFlags), std::move(fsLambda), engineLambda);
}
};
diff --git a/src/corelib/io/qfilesystemengine.cpp b/src/corelib/io/qfilesystemengine.cpp
index bedace4c79..d8b215816c 100644
--- a/src/corelib/io/qfilesystemengine.cpp
+++ b/src/corelib/io/qfilesystemengine.cpp
@@ -83,12 +83,11 @@ static inline bool _q_checkEntry(QFileSystemEntry &entry, QFileSystemMetaData &d
return true;
}
-static inline bool _q_checkEntry(QAbstractFileEngine *&engine, bool resolvingEntry)
+static inline bool _q_checkEntry(std::unique_ptr<QAbstractFileEngine> &engine, bool resolvingEntry)
{
if (resolvingEntry) {
if (!(engine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::ExistsFlag)) {
- delete engine;
- engine = nullptr;
+ engine.reset();
return false;
}
}
@@ -96,8 +95,9 @@ static inline bool _q_checkEntry(QAbstractFileEngine *&engine, bool resolvingEnt
return true;
}
-static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &entry, QFileSystemMetaData &data,
- QAbstractFileEngine *&engine, bool resolvingEntry = false)
+static bool _q_createLegacyEngine_recursive(QFileSystemEntry &entry, QFileSystemMetaData &data,
+ std::unique_ptr<QAbstractFileEngine> &engine,
+ bool resolvingEntry = false)
{
QString const &filePath = entry.filePath();
if ((engine = qt_custom_file_engine_handler_create(filePath)))
@@ -111,7 +111,7 @@ static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &ent
if (ch == u':') {
if (prefixSeparator == 0) {
- engine = new QResourceFileEngine(filePath);
+ engine = std::make_unique<QResourceFileEngine>(filePath);
return _q_checkEntry(engine, resolvingEntry);
}
@@ -123,7 +123,7 @@ static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &ent
entry = QFileSystemEntry(QDir::cleanPath(
paths.at(i) % u'/' % QStringView{filePath}.mid(prefixSeparator + 1)));
// Recurse!
- if (_q_resolveEntryAndCreateLegacyEngine_recursive(entry, data, engine, true))
+ if (_q_createLegacyEngine_recursive(entry, data, engine, true))
return true;
}
@@ -153,12 +153,13 @@ static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &ent
QFileSystemEngine API should be used to query and interact with the file
system object.
*/
-QAbstractFileEngine *QFileSystemEngine::resolveEntryAndCreateLegacyEngine(
- QFileSystemEntry &entry, QFileSystemMetaData &data) {
+std::unique_ptr<QAbstractFileEngine>
+QFileSystemEngine::createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaData &data)
+{
QFileSystemEntry copy = entry;
- QAbstractFileEngine *engine = nullptr;
+ std::unique_ptr<QAbstractFileEngine> engine;
- if (_q_resolveEntryAndCreateLegacyEngine_recursive(copy, data, engine))
+ if (_q_createLegacyEngine_recursive(copy, data, engine))
// Reset entry to resolved copy.
entry = copy;
else
diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h
index c1dddb2e1e..814915407e 100644
--- a/src/corelib/io/qfilesystemengine_p.h
+++ b/src/corelib/io/qfilesystemengine_p.h
@@ -20,6 +20,7 @@
#include "qfilesystemmetadata_p.h"
#include <QtCore/private/qsystemerror_p.h>
+#include <memory>
#include <optional>
QT_BEGIN_NAMESPACE
@@ -94,7 +95,7 @@ public:
static bool fillMetaData(int fd, QFileSystemMetaData &data); // what = PosixStatFlags
static QByteArray id(int fd);
static bool setFileTime(int fd, const QDateTime &newDate,
- QAbstractFileEngine::FileTime whatTime, QSystemError &error);
+ QFile::FileTime whatTime, QSystemError &error);
static bool setPermissions(int fd, QFile::Permissions permissions, QSystemError &error,
QFileSystemMetaData *data = nullptr);
#endif
@@ -109,7 +110,7 @@ public:
QFileSystemMetaData::MetaDataFlags what);
static QByteArray id(HANDLE fHandle);
static bool setFileTime(HANDLE fHandle, const QDateTime &newDate,
- QAbstractFileEngine::FileTime whatTime, QSystemError &error);
+ QFile::FileTime whatTime, QSystemError &error);
static QString owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own);
static QString nativeAbsoluteFilePath(const QString &path);
static bool isDirPath(const QString &path, bool *existed);
@@ -136,13 +137,14 @@ public:
// unused, therefore not implemented
static bool setFileTime(const QFileSystemEntry &entry, const QDateTime &newDate,
- QAbstractFileEngine::FileTime whatTime, QSystemError &error);
+ QFile::FileTime whatTime, QSystemError &error);
static bool setCurrentPath(const QFileSystemEntry &entry);
static QFileSystemEntry currentPath();
- static QAbstractFileEngine *resolveEntryAndCreateLegacyEngine(QFileSystemEntry &entry,
- QFileSystemMetaData &data);
+ static std::unique_ptr<QAbstractFileEngine>
+ createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaData &data);
+
private:
static QString slowCanonicalized(const QString &path);
#if defined(Q_OS_WIN)
diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp
index d94cd452cf..fb48f22bc6 100644
--- a/src/corelib/io/qfilesystemengine_unix.cpp
+++ b/src/corelib/io/qfilesystemengine_unix.cpp
@@ -12,11 +12,14 @@
#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/private/qcore_unix_p.h>
#include <QtCore/private/qfiledevice_p.h>
+#include <QtCore/private/qfunctions_p.h>
#include <QtCore/qvarlengtharray.h>
#ifndef QT_BOOTSTRAPPED
# include <QtCore/qstandardpaths.h>
+# include <QtCore/private/qtemporaryfile_p.h>
#endif // QT_BOOTSTRAPPED
+#include <grp.h>
#include <pwd.h>
#include <stdlib.h> // for realpath()
#include <unistd.h>
@@ -36,6 +39,11 @@
#if defined(Q_OS_DARWIN)
# include <QtCore/private/qcore_mac_p.h>
# include <CoreFoundation/CFBundle.h>
+# include <UniformTypeIdentifiers/UTType.h>
+# include <UniformTypeIdentifiers/UTCoreTypes.h>
+# include <Foundation/Foundation.h>
+# include <sys/clonefile.h>
+# include <copyfile.h>
#endif
#ifdef Q_OS_MACOS
@@ -46,15 +54,6 @@
#include <MobileCoreServices/MobileCoreServices.h>
#endif
-#if defined(Q_OS_DARWIN)
-# include <sys/clonefile.h>
-# include <copyfile.h>
-// We cannot include <Foundation/Foundation.h> (it's an Objective-C header), but
-// we need these declarations:
-Q_FORWARD_DECLARE_OBJC_CLASS(NSString);
-extern "C" NSString *NSTemporaryDirectory();
-#endif
-
#if defined(Q_OS_LINUX)
# include <sys/ioctl.h>
# include <sys/sendfile.h>
@@ -124,10 +123,9 @@ static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &e
QString suffix = info.suffix();
if (suffix.length() > 0) {
- // First step: is the extension known ?
- QCFType<CFStringRef> extensionRef = suffix.toCFString();
- QCFType<CFStringRef> uniformTypeIdentifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extensionRef, NULL);
- if (UTTypeConformsTo(uniformTypeIdentifier, kUTTypeBundle))
+ // First step: is it a bundle?
+ const auto *utType = [UTType typeWithFilenameExtension:suffix.toNSString()];
+ if ([utType conformsToType:UTTypeBundle])
return true;
// Second step: check if an application knows the package type
@@ -643,30 +641,17 @@ QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link,
QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
{
Q_CHECK_FILE_NAME(entry, entry);
+ char *resolved_name = nullptr;
-#if !defined(Q_OS_DARWIN) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L
- // realpath(X,0) is not supported
- Q_UNUSED(data);
- return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath()));
-#else
-# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID) || _POSIX_VERSION < 200801L
- // used to store the result of realpath in case where realpath cannot allocate itself
+#ifdef PATH_MAX
+ // use the stack to avoid the overhead of memory allocation
char stack_result[PATH_MAX + 1];
#else
- // enables unconditionally passing stack_result below
+ // system with unlimited file paths -> must use heap
std::nullptr_t stack_result = nullptr;
-# endif
- auto resolved_path_deleter = [&](char *ptr) {
- // frees resolved_name if it was allocated by realpath
-# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID) || _POSIX_VERSION < 200801L
- // ptr is either null, or points to stack_result
- Q_ASSERT(!ptr || ptr == stack_result);
- return;
-#else
- free(ptr);
-# endif
- };
- std::unique_ptr<char, decltype (resolved_path_deleter)> resolved_name {nullptr, resolved_path_deleter};
+ auto freer = qScopeGuard([&] { free(resolved_name); });
+#endif
+
# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID)
// On some Android and macOS versions, realpath() will return a path even if
// it does not exist. To work around this, we check existence in advance.
@@ -676,14 +661,14 @@ QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry,
if (!data.exists())
errno = ENOENT;
else
- resolved_name.reset(realpath(entry.nativeFilePath().constData(), stack_result));
+ resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
# else
- resolved_name.reset(realpath(entry.nativeFilePath().constData(), stack_result));
+ resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
# endif
if (resolved_name) {
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
- QString canonicalPath = QDir::cleanPath(QFile::decodeName(resolved_name.get()));
+ QString canonicalPath = QDir::cleanPath(QFile::decodeName(resolved_name));
return QFileSystemEntry(canonicalPath);
} else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
@@ -691,7 +676,6 @@ QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry,
return QFileSystemEntry();
}
return entry;
-#endif
}
//static
@@ -819,7 +803,7 @@ QString QFileSystemEngine::resolveGroupName(uint groupId)
#endif
if (gr)
return QFile::decodeName(QByteArray(gr->gr_name));
-#else // Integrity || WASM
+#else // Integrity || WASM || VxWorks
Q_UNUSED(groupId);
#endif
return QString();
@@ -1122,7 +1106,7 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, mode_t mode
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents,
std::optional<QFile::Permissions> permissions)
{
- QString dirName = entry.filePath();
+ QByteArray dirName = entry.nativeFilePath();
Q_CHECK_FILE_NAME(dirName, false);
// Darwin doesn't support trailing /'s, so remove for everyone
@@ -1130,14 +1114,13 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
dirName.chop(1);
// try to mkdir this directory
- QByteArray nativeName = QFile::encodeName(dirName);
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
- if (QT_MKDIR(nativeName, mode) == 0)
+ if (QT_MKDIR(dirName, mode) == 0)
return true;
if (!createParents)
return false;
- return createDirectoryWithParents(nativeName, mode, false);
+ return createDirectoryWithParents(dirName, mode, false);
}
//static
@@ -1177,42 +1160,169 @@ bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSy
return false;
}
-#ifndef Q_OS_DARWIN
+#ifdef Q_OS_DARWIN
+// see qfilesystemengine_mac.mm
+#elif defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD)
+// bootstrapped tools don't need this, and we don't want QStorageInfo
+//static
+bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
+ QSystemError &error)
+{
+ error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
+ return false;
+}
+#else
/*
Implementing as per https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
*/
-// bootstrapped tools don't need this, and we don't want QStorageInfo
-#ifndef QT_BOOTSTRAPPED
-static QString freeDesktopTrashLocation(const QString &sourcePath)
+namespace {
+struct FreeDesktopTrashOperation
{
- auto makeTrashDir = [](const QDir &topDir, const QString &trashDir) -> QString {
- auto ownerPerms = QFileDevice::ReadOwner
- | QFileDevice::WriteOwner
- | QFileDevice::ExeOwner;
- QString targetDir = topDir.filePath(trashDir);
- // deliberately not using mkpath, since we want to fail if topDir doesn't exist
- if (topDir.mkdir(trashDir))
- QFile::setPermissions(targetDir, ownerPerms);
- if (QFileInfo(targetDir).isDir())
- return targetDir;
- return QString();
- };
- auto isSticky = [](const QFileInfo &fileInfo) -> bool {
- struct stat st;
- if (stat(QFile::encodeName(fileInfo.absoluteFilePath()).constData(), &st) == 0)
- return st.st_mode & S_ISVTX;
+ /*
+ "A trash directory contains two subdirectories, named info and files."
+ */
+ QString trashPath;
+ int filesDirFd = -1;
+ int infoDirFd = -1;
+ qsizetype volumePrefixLength = 0;
- return false;
- };
+ // relative file paths to the filesDirFd and infoDirFd from above
+ QByteArray tempTrashFileName;
+ QByteArray infoFilePath;
+
+ int infoFileFd = -1; // if we've already opened it
+ ~FreeDesktopTrashOperation()
+ {
+ close();
+ }
+
+ constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
+
+ void close()
+ {
+ int savedErrno = errno;
+ if (infoFileFd != -1) {
+ Q_ASSERT(infoDirFd != -1);
+ Q_ASSERT(!infoFilePath.isEmpty());
+ Q_ASSERT(!trashPath.isEmpty());
+
+ QT_CLOSE(infoFileFd);
+ unlinkat(infoDirFd, infoFilePath, 0);
+ infoFileFd = -1;
+ }
+ if (!tempTrashFileName.isEmpty()) {
+ Q_ASSERT(filesDirFd != -1);
+ unlinkat(filesDirFd, tempTrashFileName, 0);
+ }
+ if (filesDirFd >= 0)
+ QT_CLOSE(filesDirFd);
+ if (infoDirFd >= 0)
+ QT_CLOSE(infoDirFd);
+ filesDirFd = infoDirFd = -1;
+ errno = savedErrno;
+ }
+
+ bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
+ {
+ QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
+ infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
+ if (infoFileFd < 0) {
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ return false;
+ }
+ infoFilePath = std::move(p);
+ return true;
+ }
+
+ void commit()
+ {
+ QT_CLOSE(infoFileFd);
+ infoFileFd = -1;
+ tempTrashFileName = {};
+ }
+
+ // opens a directory and returns the file descriptor
+ static int openDirFd(int dfd, const char *path, int mode = 0)
+ {
+ mode |= QT_OPEN_RDONLY | O_NOFOLLOW | O_DIRECTORY;
+ return qt_safe_openat(dfd, path, mode);
+ }
+
+ // opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
+ static int openOrCreateDir(int dfd, const char *path)
+ {
+ // try to open it as a dir, first
+ int fd = openDirFd(dfd, path);
+ if (fd >= 0 || errno != ENOENT)
+ return fd;
+
+ // try to mkdirat
+ if (mkdirat(dfd, path, 0700) < 0)
+ return -1;
+
+ // try to open it again
+ return openDirFd(dfd, path);
+ }
+
+ // opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
+ bool getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
+ QSystemError &error)
+ {
+ if (parentfd == AT_FDCWD)
+ trashPath = targetDir;
+ QByteArray nativePath = QFile::encodeName(targetDir);
+
+ // open the directory
+ int trashfd = openOrCreateDir(parentfd, nativePath);
+ if (trashfd < 0 && errno != ENOENT) {
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ return false;
+ }
+
+ // check if it is ours (even if we've just mkdirat'ed it)
+ if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0) {
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ return false;
+ } else if (st.st_uid != getuid()) {
+ error = QSystemError(EPERM, QSystemError::StandardLibraryError);
+ return false;
+ }
+
+ filesDirFd = openOrCreateDir(trashfd, "files");
+ if (filesDirFd >= 0) {
+ // try to link our file-to-be-trashed here
+ QTemporaryFileName tfn("XXXXXX"_L1);
+ for (int i = 0; i < 16; ++i) {
+ QByteArray attempt = tfn.generateNext();
+ if (linkat(AT_FDCWD, source.nativeFilePath(), filesDirFd, attempt, 0) == 0) {
+ tempTrashFileName = std::move(attempt);
+ break;
+ }
+ if (errno != EEXIST)
+ break;
+ }
+
+ // man 2 link on Linux has:
+ // EPERM The filesystem containing oldpath and newpath does not
+ // support the creation of hard links.
+ // EPERM oldpath is a directory.
+ // EPERM oldpath is marked immutable or append‐only.
+ // EMLINK The file referred to by oldpath already has the maximum
+ // number of links to it.
+ if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
+ infoDirFd = openOrCreateDir(trashfd, "info");
+ }
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ if (infoDirFd < 0)
+ close();
+ QT_CLOSE(trashfd);
+ return infoDirFd >= 0;
+ }
- QString trash;
- const QStorageInfo sourceStorage(sourcePath);
- const QStorageInfo homeStorage(QDir::home());
- // We support trashing of files outside the users home partition
- if (sourceStorage != homeStorage) {
- const auto dotTrash = ".Trash"_L1;
- QDir topDir(sourceStorage.rootPath());
+ bool openMountPointTrashLocation(const QFileSystemEntry &source,
+ const QStorageInfo &sourceStorage, QSystemError &error)
+ {
/*
Method 1:
"An administrator can create an $topdir/.Trash directory. The permissions on this
@@ -1223,21 +1333,31 @@ static QString freeDesktopTrashLocation(const QString &sourcePath)
(if it supports trashing in top directories) MUST check for the presence
of $topdir/.Trash."
*/
- const QString userID = QString::number(::getuid());
- if (topDir.cd(dotTrash)) {
- const QFileInfo trashInfo(topDir.path());
- // we MUST check that the sticky bit is set, and that it is not a symlink
- if (trashInfo.isSymLink()) {
+ const auto dotTrash = "/.Trash"_L1;
+ const QString userID = QString::number(::getuid());
+ QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
+
+ // we MUST check that the sticky bit is set, and that it is not a symlink
+ int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath());
+ QT_STATBUF st = {};
+ if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
+ // O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
+ if (QT_LSTAT(dotTrashDir.nativeFilePath(), &st) == 0 && S_ISLNK(st.st_mode)) {
// we SHOULD report the failed check to the administrator
qCritical("Warning: '%s' is a symlink to '%s'",
- trashInfo.absoluteFilePath().toLocal8Bit().constData(),
- trashInfo.symLinkTarget().toLatin1().constData());
- } else if (!isSticky(trashInfo)) {
+ dotTrashDir.nativeFilePath().constData(),
+ qt_readlink(dotTrashDir.nativeFilePath()).constData());
+ error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
+ }
+ } else if (genericTrashFd >= 0) {
+ QT_FSTAT(genericTrashFd, &st);
+ if ((st.st_mode & S_ISVTX) == 0) {
// we SHOULD report the failed check to the administrator
qCritical("Warning: '%s' doesn't have sticky bit set!",
- trashInfo.absoluteFilePath().toLocal8Bit().constData());
- } else if (trashInfo.isDir()) {
+ dotTrashDir.nativeFilePath().constData());
+ error = QSystemError(EPERM, QSystemError::StandardLibraryError);
+ } else {
/*
"If the directory exists and passes the checks, a subdirectory of the
$topdir/.Trash directory is to be used as the user's trash directory
@@ -1247,9 +1367,14 @@ static QString freeDesktopTrashLocation(const QString &sourcePath)
the implementation MUST immediately create it, without any warnings or
delays for the user."
*/
- trash = makeTrashDir(topDir, userID);
+ if (getTrashDir(genericTrashFd, userID, source, error)) {
+ // recreate the resulting path
+ trashPath = dotTrashDir.filePath() + u'/' + userID;
+ }
}
+ QT_CLOSE(genericTrashFd);
}
+
/*
Method 2:
"If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
@@ -1257,145 +1382,146 @@ static QString freeDesktopTrashLocation(const QString &sourcePath)
file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
immediately create it, without any warnings or delays for the user."
*/
- if (trash.isEmpty()) {
- topDir = QDir(sourceStorage.rootPath());
- const QString userTrashDir = dotTrash + u'-' + userID;
- trash = makeTrashDir(topDir, userTrashDir);
+ if (!isTrashDirOpen())
+ getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source, error);
+
+ if (isTrashDirOpen()) {
+ volumePrefixLength = sourceStorage.rootPath().size();
+ if (volumePrefixLength == 1)
+ volumePrefixLength = 0; // isRoot
+ else
+ ++volumePrefixLength; // to include the slash
}
+ return isTrashDirOpen();
}
- /*
- "If both (1) and (2) fail [...], the implementation MUST either trash the
- file into the user's “home trash” or refuse to trash it."
- We trash the file into the user's home trash.
-
- "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
- QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
- we are not running on a freedesktop.org-compliant environment, and give up.
- */
- if (trash.isEmpty()) {
- QDir topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
- trash = makeTrashDir(topDir, "Trash"_L1);
- if (!QFileInfo(trash).isDir()) {
- qWarning("Unable to establish trash directory in %s",
- topDir.path().toLocal8Bit().constData());
- }
+ bool openHomeTrashLocation(const QFileSystemEntry &source, QSystemError &error)
+ {
+ QString topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
+ return getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, error);
}
- return trash;
-}
-#endif // QT_BOOTSTRAPPED
+ bool findTrashFor(const QFileSystemEntry &source, QSystemError &error)
+ {
+ /*
+ First, try the standard Trash in $XDG_DATA_DIRS:
+ "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
+ QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
+ we are not running on a freedesktop.org-compliant environment, and give up.
+ */
+ if (openHomeTrashLocation(source, error))
+ return true;
+ if (error.errorCode != EXDEV)
+ return false;
+
+ // didn't work, try to find the trash outside the home filesystem
+ const QStorageInfo sourceStorage(source.filePath());
+ if (!sourceStorage.isValid())
+ return false;
+ return openMountPointTrashLocation(source, sourceStorage, error);
+ }
+};
+} // unnamed namespace
//static
bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
QFileSystemEntry &newLocation, QSystemError &error)
{
-#ifdef QT_BOOTSTRAPPED
- Q_UNUSED(source);
- Q_UNUSED(newLocation);
- error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
- return false;
-#else
- const QFileInfo sourceInfo(source.filePath());
- if (!sourceInfo.exists()) {
- error = QSystemError(ENOENT, QSystemError::StandardLibraryError);
+ const QFileSystemEntry sourcePath = [&] {
+ if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
+ path.chop(1);
+ return absoluteName(QFileSystemEntry(path));
+ }
+ return absoluteName(source);
+ }();
+ FreeDesktopTrashOperation op;
+ if (!op.findTrashFor(sourcePath, error))
return false;
- }
- const QString sourcePath = sourceInfo.absoluteFilePath();
- QDir trashDir(freeDesktopTrashLocation(sourcePath));
- if (!trashDir.exists())
- return false;
- /*
- "A trash directory contains two subdirectories, named info and files."
- */
- const auto filesDir = "files"_L1;
- const auto infoDir = "info"_L1;
- trashDir.mkdir(filesDir);
- int savedErrno = errno;
- trashDir.mkdir(infoDir);
- if (!savedErrno)
- savedErrno = errno;
- if (!trashDir.exists(filesDir) || !trashDir.exists(infoDir)) {
- error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
- return false;
- }
/*
"The $trash/files directory contains the files and directories that were trashed.
The names of files in this directory are to be determined by the implementation;
the only limitation is that they must be unique within the directory. Even if a
file with the same name and location gets trashed many times, each subsequent
trashing must not overwrite a previous copy."
- */
- const QString trashedName = sourceInfo.isDir()
- ? QDir(sourcePath).dirName()
- : sourceInfo.fileName();
- QString uniqueTrashedName = u'/' + trashedName;
- QString infoFileName;
- int counter = 0;
- QFile infoFile;
- auto makeUniqueTrashedName = [trashedName, &counter]() -> QString {
- return QString::asprintf("/%ls-%04d", qUtf16Printable(trashedName), ++counter);
- };
- do {
- while (QFile::exists(trashDir.filePath(filesDir) + uniqueTrashedName))
- uniqueTrashedName = makeUniqueTrashedName();
- /*
- "The $trash/info directory contains an "information file" for every file and directory
- in $trash/files. This file MUST have exactly the same name as the file or directory in
- $trash/files, plus the extension ".trashinfo"
- [...]
- When trashing a file or directory, the implementation MUST create the corresponding
- file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
- so that if two processes try to trash files with the same filename this will result
- in two different trash files. On Unix-like systems this is done by generating a
- filename, and then opening with O_EXCL. If that succeeds the creation was atomic
- (at least on the same machine), if it fails you need to pick another filename."
- */
- infoFileName = trashDir.filePath(infoDir)
- + uniqueTrashedName + ".trashinfo"_L1;
- infoFile.setFileName(infoFileName);
- if (!infoFile.open(QIODevice::NewOnly | QIODevice::WriteOnly | QIODevice::Text))
- uniqueTrashedName = makeUniqueTrashedName();
- } while (!infoFile.isOpen());
-
- const QString targetPath = trashDir.filePath(filesDir) + uniqueTrashedName;
- const QFileSystemEntry target(targetPath);
-
- QString infoPath;
- const QStorageInfo storageInfo(sourcePath);
- if (storageInfo.isValid() && storageInfo.rootPath() != rootPath() && storageInfo != QStorageInfo(QDir::home())) {
- infoPath = sourcePath.mid(storageInfo.rootPath().length());
- if (infoPath.front() == u'/')
- infoPath = infoPath.mid(1);
- } else {
- infoPath = sourcePath;
- }
- /*
- We might fail to rename if source and target are on different file systems.
- In that case, we don't try further, i.e. copying and removing the original
- is usually not what the user would expect to happen.
+ We first try the unchanged base name, then try something different if it collides.
+
+ "The $trash/info directory contains an "information file" for every file and directory
+ in $trash/files. This file MUST have exactly the same name as the file or directory in
+ $trash/files, plus the extension ".trashinfo"
+ [...]
+ When trashing a file or directory, the implementation MUST create the corresponding
+ file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
+ so that if two processes try to trash files with the same filename this will result
+ in two different trash files. On Unix-like systems this is done by generating a
+ filename, and then opening with O_EXCL. If that succeeds the creation was atomic
+ (at least on the same machine), if it fails you need to pick another filename."
*/
- if (!renameFile(source, target, error)) {
- infoFile.close();
- infoFile.remove();
- return false;
+ QString uniqueTrashedName = sourcePath.fileName();
+ if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
+ // we'll use a counter, starting with the file's inode number to avoid
+ // collisions
+ qulonglong counter;
+ if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
+ counter = st.st_ino;
+ } else {
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ return false;
+ }
+
+ QString uniqueTrashBase = std::move(uniqueTrashedName);
+ for (;;) {
+ uniqueTrashedName = QString::asprintf("%ls-%llu", qUtf16Printable(uniqueTrashBase),
+ counter++);
+ if (op.tryCreateInfoFile(uniqueTrashedName, error))
+ break;
+ if (error.errorCode != EEXIST)
+ return false;
+ };
}
QByteArray info =
"[Trash Info]\n"
- "Path=" + QUrl::toPercentEncoding(infoPath, "/") + "\n"
- "DeletionDate=" + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"_L1).toUtf8()
+ "Path=" + QUrl::toPercentEncoding(source.filePath().mid(op.volumePrefixLength), "/") + "\n"
+ "DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8()
+ "\n";
- infoFile.write(info);
- infoFile.close();
+ if (QT_WRITE(op.infoFileFd, info.data(), info.size()) < 0) {
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ return false;
+ }
- newLocation = QFileSystemEntry(targetPath);
+ /*
+ If we've already linked the file-to-be-trashed into the trash
+ directory, we know it's in the same mountpoint and we won't get ENOSPC
+ renaming the temporary file to the target name either.
+ */
+ bool renamed;
+ if (op.tempTrashFileName.isEmpty()) {
+ /*
+ We did not get a link (we're trying to trash a directory or on a
+ filesystem that doesn't support hardlinking), so rename straight
+ from the original name. We might fail to rename if source and target
+ are on different file systems.
+ */
+ renamed = renameat(AT_FDCWD, source.nativeFilePath(), op.filesDirFd,
+ QFile::encodeName(uniqueTrashedName)) == 0;
+ } else {
+ renamed = renameat(op.filesDirFd, op.tempTrashFileName, op.filesDirFd,
+ QFile::encodeName(uniqueTrashedName)) == 0;
+ if (renamed)
+ removeFile(source, error); // success, delete the original file
+ }
+ if (!renamed) {
+ error = QSystemError(errno, QSystemError::StandardLibraryError);
+ return false;
+ }
+
+ op.commit();
+ newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
return true;
-#endif // QT_BOOTSTRAPPED
}
-#endif // Q_OS_DARWIN
+#endif // !Q_OS_DARWIN && !QT_BOOTSTRAPPED
//static
bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
@@ -1543,10 +1669,10 @@ bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, Q
}
//static
-bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QAbstractFileEngine::FileTime time, QSystemError &error)
+bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
{
- if (!newDate.isValid() || time == QAbstractFileEngine::BirthTime ||
- time == QAbstractFileEngine::MetadataChangeTime) {
+ if (!newDate.isValid()
+ || time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
return false;
}
@@ -1555,8 +1681,8 @@ bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QAbstractF
// UTIME_OMIT: leave file timestamp unchanged
struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
- if (time == QAbstractFileEngine::AccessTime || time == QAbstractFileEngine::ModificationTime) {
- const int idx = time == QAbstractFileEngine::AccessTime ? 0 : 1;
+ if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
+ const int idx = time == QFile::FileAccessTime ? 0 : 1;
const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
ts[idx] = durationToTimespec(msecs);
}
diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp
index 49f375c4bb..3ec32e31a1 100644
--- a/src/corelib/io/qfilesystemengine_win.cpp
+++ b/src/corelib/io/qfilesystemengine_win.cpp
@@ -390,12 +390,6 @@ static QBasicAtomicInt qt_ntfs_permission_lookup_v2 = Q_BASIC_ATOMIC_INITIALIZER
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
-/*!
- \internal
-
- Returns true if the check was previously enabled.
-*/
-
bool qEnableNtfsPermissionChecks() noexcept
{
return qt_ntfs_permission_lookup_v2.fetchAndAddRelaxed(1)
@@ -403,12 +397,6 @@ QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup)
!= 0;
}
-/*!
- \internal
-
- Returns true if the check is disabled, i.e. there are no more users.
-*/
-
bool qDisableNtfsPermissionChecks() noexcept
{
return qt_ntfs_permission_lookup_v2.fetchAndSubRelaxed(1)
@@ -416,12 +404,6 @@ QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup)
== 1;
}
-/*!
- \internal
-
- Returns true if the check is enabled.
-*/
-
bool qAreNtfsPermissionChecksEnabled() noexcept
{
return qt_ntfs_permission_lookup_v2.loadRelaxed()
@@ -840,9 +822,10 @@ public:
return (dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE) ? S_OK : E_FAIL;
}
HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD /* dwFlags */, IShellItem * /* psiItem */,
- HRESULT /* hrDelete */,
+ HRESULT hrDelete,
IShellItem *psiNewlyCreated) override
{
+ deleteResult = hrDelete;
if (psiNewlyCreated) {
wchar_t *pszName = nullptr;
psiNewlyCreated->GetDisplayName(SIGDN_FILESYSPATH, &pszName);
@@ -863,6 +846,7 @@ public:
HRESULT STDMETHODCALLTYPE ResumeTimer() override { return S_OK; }
QString targetPath;
+ HRESULT deleteResult = S_OK;
private:
ULONG ref;
};
@@ -1085,7 +1069,7 @@ QByteArray QFileSystemEngine::id(HANDLE fHandle)
//static
bool QFileSystemEngine::setFileTime(HANDLE fHandle, const QDateTime &newDate,
- QAbstractFileEngine::FileTime time, QSystemError &error)
+ QFile::FileTime time, QSystemError &error)
{
FILETIME fTime;
FILETIME *pLastWrite = nullptr;
@@ -1093,15 +1077,15 @@ bool QFileSystemEngine::setFileTime(HANDLE fHandle, const QDateTime &newDate,
FILETIME *pCreationTime = nullptr;
switch (time) {
- case QAbstractFileEngine::ModificationTime:
+ case QFile::FileModificationTime:
pLastWrite = &fTime;
break;
- case QAbstractFileEngine::AccessTime:
+ case QFile::FileAccessTime:
pLastAccess = &fTime;
break;
- case QAbstractFileEngine::BirthTime:
+ case QFile::FileBirthTime:
pCreationTime = &fTime;
break;
@@ -1833,8 +1817,12 @@ bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
hres = pfo->PerformOperations();
if (!SUCCEEDED(hres))
return false;
- newLocation = QFileSystemEntry(sink->targetPath);
+ if (!SUCCEEDED(sink->deleteResult)) {
+ error = QSystemError(sink->deleteResult, QSystemError::NativeError);
+ return false;
+ }
+ newLocation = QFileSystemEntry(sink->targetPath);
return true;
}
diff --git a/src/corelib/io/qfilesystementry.cpp b/src/corelib/io/qfilesystementry.cpp
index 6a655391be..ac1691d30e 100644
--- a/src/corelib/io/qfilesystementry.cpp
+++ b/src/corelib/io/qfilesystementry.cpp
@@ -14,6 +14,10 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+// Assigned to m_lastSeparator and m_firstDotInFileName to indicate resolveFilePath()
+// hasn't been called yet
+constexpr int Uninitialized = -2;
+
#ifdef Q_OS_WIN
static bool isUncRoot(const QString &server)
{
@@ -51,8 +55,8 @@ QFileSystemEntry::QFileSystemEntry()
*/
QFileSystemEntry::QFileSystemEntry(const QString &filePath)
: m_filePath(QDir::fromNativeSeparators(filePath)),
- m_lastSeparator(-2),
- m_firstDotInFileName(-2),
+ m_lastSeparator(Uninitialized),
+ m_firstDotInFileName(Uninitialized),
m_lastDotInFileName(0)
{
}
@@ -64,8 +68,8 @@ QFileSystemEntry::QFileSystemEntry(const QString &filePath)
*/
QFileSystemEntry::QFileSystemEntry(const QString &filePath, FromInternalPath /* dummy */)
: m_filePath(filePath),
- m_lastSeparator(-2),
- m_firstDotInFileName(-2),
+ m_lastSeparator(Uninitialized),
+ m_firstDotInFileName(Uninitialized),
m_lastDotInFileName(0)
{
}
@@ -76,8 +80,8 @@ QFileSystemEntry::QFileSystemEntry(const QString &filePath, FromInternalPath /*
*/
QFileSystemEntry::QFileSystemEntry(const NativePath &nativeFilePath, FromNativePath /* dummy */)
: m_nativeFilePath(nativeFilePath),
- m_lastSeparator(-2),
- m_firstDotInFileName(-2),
+ m_lastSeparator(Uninitialized),
+ m_firstDotInFileName(Uninitialized),
m_lastDotInFileName(0)
{
}
@@ -85,8 +89,8 @@ QFileSystemEntry::QFileSystemEntry(const NativePath &nativeFilePath, FromNativeP
QFileSystemEntry::QFileSystemEntry(const QString &filePath, const NativePath &nativeFilePath)
: m_filePath(QDir::fromNativeSeparators(filePath)),
m_nativeFilePath(nativeFilePath),
- m_lastSeparator(-2),
- m_firstDotInFileName(-2),
+ m_lastSeparator(Uninitialized),
+ m_firstDotInFileName(Uninitialized),
m_lastDotInFileName(0)
{
}
@@ -312,7 +316,7 @@ bool QFileSystemEntry::isEmpty() const
void QFileSystemEntry::findLastSeparator() const
{
- if (m_lastSeparator == -2) {
+ if (m_lastSeparator == Uninitialized) {
resolveFilePath();
m_lastSeparator = m_filePath.lastIndexOf(u'/');
}
@@ -320,7 +324,7 @@ void QFileSystemEntry::findLastSeparator() const
void QFileSystemEntry::findFileNameSeparators() const
{
- if (m_firstDotInFileName == -2) {
+ if (m_firstDotInFileName == Uninitialized) {
resolveFilePath();
int firstDotInFileName = -1;
int lastDotInFileName = -1;
diff --git a/src/corelib/io/qfilesystemiterator_p.h b/src/corelib/io/qfilesystemiterator_p.h
index 2792340637..2973b85cd2 100644
--- a/src/corelib/io/qfilesystemiterator_p.h
+++ b/src/corelib/io/qfilesystemiterator_p.h
@@ -20,44 +20,48 @@
#ifndef QT_NO_FILESYSTEMITERATOR
#include <QtCore/qdir.h>
-#include <QtCore/qdiriterator.h>
#include <QtCore/qstringlist.h>
#include <QtCore/private/qfilesystementry_p.h>
#include <QtCore/private/qfilesystemmetadata_p.h>
-// Platform-specific headers
#if !defined(Q_OS_WIN)
-#include <QtCore/qscopedpointer.h>
+#include <private/qstringconverter_p.h>
#endif
+#include <memory>
+
QT_BEGIN_NAMESPACE
class QFileSystemIterator
{
public:
- QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters,
- const QStringList &nameFilters, QDirIterator::IteratorFlags flags
- = QDirIterator::FollowSymlinks | QDirIterator::Subdirectories);
+ QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters);
~QFileSystemIterator();
bool advance(QFileSystemEntry &fileEntry, QFileSystemMetaData &metaData);
private:
- QFileSystemEntry::NativePath nativePath;
+ QString dirPath;
// Platform-specific data
#if defined(Q_OS_WIN)
- QString dirPath;
+ QFileSystemEntry::NativePath nativePath;
HANDLE findFileHandle;
QStringList uncShares;
bool uncFallback;
int uncShareIndex;
bool onlyDirs;
#else
- QT_DIR *dir;
- QT_DIRENT *dirEntry;
- int lastError;
+ struct DirStreamCloser {
+ void operator()(QT_DIR *dir) { if (dir) QT_CLOSEDIR(dir); }
+ };
+ using DirPtr = std::unique_ptr<QT_DIR, DirStreamCloser>;
+ DirPtr dir;
+
+ QT_DIRENT *dirEntry = nullptr;
+ int lastError = 0;
+ QStringDecoder toUtf16;
#endif
Q_DISABLE_COPY_MOVE(QFileSystemIterator)
diff --git a/src/corelib/io/qfilesystemiterator_unix.cpp b/src/corelib/io/qfilesystemiterator_unix.cpp
index 6d6878efda..a1130728ef 100644
--- a/src/corelib/io/qfilesystemiterator_unix.cpp
+++ b/src/corelib/io/qfilesystemiterator_unix.cpp
@@ -4,10 +4,10 @@
#include "qplatformdefs.h"
#include "qfilesystemiterator_p.h"
-#include <private/qstringconverter_p.h>
-
#ifndef QT_NO_FILESYSTEMITERATOR
+#include <qvarlengtharray.h>
+
#include <memory>
#include <stdlib.h>
@@ -15,53 +15,66 @@
QT_BEGIN_NAMESPACE
-static bool checkNameDecodable(const char *d_name, qsizetype len)
-{
- // This function is called in a loop from advance() below, but the loop is
- // usually run only once.
-
- return QUtf8::isValidUtf8(QByteArrayView(d_name, len)).isValidUtf8;
-}
-
-QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters,
- const QStringList &nameFilters, QDirIterator::IteratorFlags flags)
- : nativePath(entry.nativeFilePath())
- , dir(nullptr)
- , dirEntry(nullptr)
- , lastError(0)
+/*
+ Native filesystem iterator, which uses ::opendir()/readdir()/dirent from the system
+ libraries to iterate over the directory represented by \a entry.
+*/
+QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters)
+ : dirPath(entry.filePath()),
+ toUtf16(QStringDecoder::Utf8)
{
Q_UNUSED(filters);
- Q_UNUSED(nameFilters);
- Q_UNUSED(flags);
- if ((dir = QT_OPENDIR(nativePath.constData())) == nullptr) {
+ dir.reset(QT_OPENDIR(entry.nativeFilePath().constData()));
+ if (!dir) {
lastError = errno;
} else {
- if (!nativePath.endsWith('/'))
- nativePath.append('/');
+ if (!dirPath.endsWith(u'/'))
+ dirPath.append(u'/');
}
}
-QFileSystemIterator::~QFileSystemIterator()
-{
- if (dir)
- QT_CLOSEDIR(dir);
-}
+QFileSystemIterator::~QFileSystemIterator() = default;
bool QFileSystemIterator::advance(QFileSystemEntry &fileEntry, QFileSystemMetaData &metaData)
{
+ auto asFileEntry = [this](QStringView name) {
+#ifdef Q_OS_DARWIN
+ // must match QFile::decodeName
+ QString normalized = name.toString().normalized(QString::NormalizationForm_C);
+ name = normalized;
+#endif
+ return QFileSystemEntry(dirPath + name, QFileSystemEntry::FromInternalPath());
+ };
if (!dir)
return false;
for (;;) {
- dirEntry = QT_READDIR(dir);
+ // From readdir man page:
+ // If the end of the directory stream is reached, NULL is returned and errno is
+ // not changed. If an error occurs, NULL is returned and errno is set to indicate
+ // the error. To distinguish end of stream from an error, set errno to zero before
+ // calling readdir() and then check the value of errno if NULL is returned.
+ errno = 0;
+ dirEntry = QT_READDIR(dir.get());
if (dirEntry) {
- qsizetype len = strlen(dirEntry->d_name);
- if (checkNameDecodable(dirEntry->d_name, len)) {
- fileEntry = QFileSystemEntry(nativePath + QByteArray(dirEntry->d_name, len), QFileSystemEntry::FromNativePath());
+ // POSIX allows readdir() to return a file name in struct dirent that
+ // extends past the end of the d_name array (it's a char[1] array on QNX, for
+ // example). Therefore, we *must* call strlen() on it to get the actual length
+ // of the file name. See:
+ // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html#tag_13_07_05
+ QByteArrayView name(dirEntry->d_name, strlen(dirEntry->d_name));
+ // name.size() is sufficient here, see QUtf8::convertToUnicode() for details
+ QVarLengthArray<char16_t> buffer(name.size());
+ auto *end = toUtf16.appendToBuffer(buffer.data(), name);
+ buffer.resize(end - buffer.constData());
+ if (!toUtf16.hasError()) {
+ fileEntry = asFileEntry(buffer);
metaData.fillFromDirEnt(*dirEntry);
return true;
+ } else {
+ errno = EILSEQ; // Invalid or incomplete multibyte or wide character
}
} else {
break;
diff --git a/src/corelib/io/qfilesystemiterator_win.cpp b/src/corelib/io/qfilesystemiterator_win.cpp
index 2c16cc7f6b..7644a1a078 100644
--- a/src/corelib/io/qfilesystemiterator_win.cpp
+++ b/src/corelib/io/qfilesystemiterator_win.cpp
@@ -14,17 +14,14 @@ using namespace Qt::StringLiterals;
bool done = true;
-QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters,
- const QStringList &nameFilters, QDirIterator::IteratorFlags flags)
- : nativePath(entry.nativeFilePath())
- , dirPath(entry.filePath())
+QFileSystemIterator::QFileSystemIterator(const QFileSystemEntry &entry, QDir::Filters filters)
+ : dirPath(entry.filePath())
+ , nativePath(entry.nativeFilePath())
, findFileHandle(INVALID_HANDLE_VALUE)
, uncFallback(false)
, uncShareIndex(0)
, onlyDirs(false)
{
- Q_UNUSED(nameFilters);
- Q_UNUSED(flags);
if (nativePath.endsWith(u".lnk"_s) && !QFileSystemEngine::isDirPath(dirPath, nullptr)) {
QFileSystemMetaData metaData;
QFileSystemEntry link = QFileSystemEngine::getLinkTarget(entry, metaData);
diff --git a/src/corelib/io/qfilesystemmetadata_p.h b/src/corelib/io/qfilesystemmetadata_p.h
index cb19b98c33..c10b66afaf 100644
--- a/src/corelib/io/qfilesystemmetadata_p.h
+++ b/src/corelib/io/qfilesystemmetadata_p.h
@@ -186,11 +186,15 @@ public:
QDateTime metadataChangeTime() const;
QDateTime modificationTime() const;
- QDateTime fileTime(QAbstractFileEngine::FileTime time) const;
+ QDateTime fileTime(QFile::FileTime time) const;
uint userId() const;
uint groupId() const;
uint ownerId(QAbstractFileEngine::FileOwner owner) const;
+ bool isReadable() const { return permissions().testAnyFlags(QFile::ReadUser); }
+ bool isWritable() const { return permissions().testAnyFlags(QFile::WriteUser); }
+ bool isExecutable() const { return permissions().testAnyFlags(QFile::ExeUser); }
+
#ifdef Q_OS_UNIX
void fillFromStatxBuf(const struct statx &statBuffer);
void fillFromStatBuf(const QT_STATBUF &statBuffer);
@@ -243,19 +247,19 @@ inline bool QFileSystemMetaData::isAlias() const { return fal
#endif
#if defined(Q_OS_UNIX) || defined (Q_OS_WIN)
-inline QDateTime QFileSystemMetaData::fileTime(QAbstractFileEngine::FileTime time) const
+inline QDateTime QFileSystemMetaData::fileTime(QFile::FileTime time) const
{
switch (time) {
- case QAbstractFileEngine::ModificationTime:
+ case QFile::FileModificationTime:
return modificationTime();
- case QAbstractFileEngine::AccessTime:
+ case QFile::FileAccessTime:
return accessTime();
- case QAbstractFileEngine::BirthTime:
+ case QFile::FileBirthTime:
return birthTime();
- case QAbstractFileEngine::MetadataChangeTime:
+ case QFile::FileMetadataChangeTime:
return metadataChangeTime();
}
diff --git a/src/corelib/io/qfilesystemwatcher.cpp b/src/corelib/io/qfilesystemwatcher.cpp
index 9ffbe31d3d..7138f8260b 100644
--- a/src/corelib/io/qfilesystemwatcher.cpp
+++ b/src/corelib/io/qfilesystemwatcher.cpp
@@ -58,29 +58,29 @@ QFileSystemWatcherPrivate::QFileSystemWatcherPrivate()
{
}
+void QFileSystemWatcherPrivate::connectEngine(QFileSystemWatcherEngine *engine)
+{
+ QObjectPrivate::connect(engine, &QFileSystemWatcherEngine::fileChanged,
+ this, &QFileSystemWatcherPrivate::fileChanged);
+ QObjectPrivate::connect(engine, &QFileSystemWatcherEngine::directoryChanged,
+ this, &QFileSystemWatcherPrivate::directoryChanged);
+}
+
void QFileSystemWatcherPrivate::init()
{
Q_Q(QFileSystemWatcher);
native = createNativeEngine(q);
if (native) {
- QObject::connect(native,
- SIGNAL(fileChanged(QString,bool)),
- q,
- SLOT(_q_fileChanged(QString,bool)));
- QObject::connect(native,
- SIGNAL(directoryChanged(QString,bool)),
- q,
- SLOT(_q_directoryChanged(QString,bool)));
+ connectEngine(native);
#if defined(Q_OS_WIN)
- QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
- &QWindowsFileSystemWatcherEngine::driveLockForRemoval,
- q, [this] (const QString &p) { _q_winDriveLockForRemoval(p); });
- QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
- &QWindowsFileSystemWatcherEngine::driveLockForRemovalFailed,
- q, [this] (const QString &p) { _q_winDriveLockForRemovalFailed(p); });
- QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
- &QWindowsFileSystemWatcherEngine::driveRemoved,
- q, [this] (const QString &p) { _q_winDriveRemoved(p); });
+ auto *windowsWatcher = static_cast<QWindowsFileSystemWatcherEngine *>(native);
+ using WinE = QWindowsFileSystemWatcherEngine;
+ QObjectPrivate::connect(windowsWatcher, &WinE::driveLockForRemoval,
+ this, &QFileSystemWatcherPrivate::winDriveLockForRemoval);
+ QObjectPrivate::connect(windowsWatcher, &WinE::driveLockForRemovalFailed,
+ this, &QFileSystemWatcherPrivate::winDriveLockForRemovalFailed);
+ QObjectPrivate::connect(windowsWatcher, &WinE::driveRemoved,
+ this, &QFileSystemWatcherPrivate::winDriveRemoved);
#endif // Q_OS_WIN
}
}
@@ -92,17 +92,10 @@ void QFileSystemWatcherPrivate::initPollerEngine()
Q_Q(QFileSystemWatcher);
poller = new QPollingFileSystemWatcherEngine(q); // that was a mouthful
- QObject::connect(poller,
- SIGNAL(fileChanged(QString,bool)),
- q,
- SLOT(_q_fileChanged(QString,bool)));
- QObject::connect(poller,
- SIGNAL(directoryChanged(QString,bool)),
- q,
- SLOT(_q_directoryChanged(QString,bool)));
+ connectEngine(poller);
}
-void QFileSystemWatcherPrivate::_q_fileChanged(const QString &path, bool removed)
+void QFileSystemWatcherPrivate::fileChanged(const QString &path, bool removed)
{
Q_Q(QFileSystemWatcher);
qCDebug(lcWatcher) << "file changed" << path << "removed?" << removed << "watching?" << files.contains(path);
@@ -115,7 +108,7 @@ void QFileSystemWatcherPrivate::_q_fileChanged(const QString &path, bool removed
emit q->fileChanged(path, QFileSystemWatcher::QPrivateSignal());
}
-void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool removed)
+void QFileSystemWatcherPrivate::directoryChanged(const QString &path, bool removed)
{
Q_Q(QFileSystemWatcher);
qCDebug(lcWatcher) << "directory changed" << path << "removed?" << removed << "watching?" << directories.contains(path);
@@ -130,7 +123,7 @@ void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool re
#if defined(Q_OS_WIN)
-void QFileSystemWatcherPrivate::_q_winDriveLockForRemoval(const QString &path)
+void QFileSystemWatcherPrivate::winDriveLockForRemoval(const QString &path)
{
// Windows: Request to lock a (removable/USB) drive for removal, release
// its paths under watch, temporarily storing them should the lock fail.
@@ -147,7 +140,7 @@ void QFileSystemWatcherPrivate::_q_winDriveLockForRemoval(const QString &path)
}
}
-void QFileSystemWatcherPrivate::_q_winDriveLockForRemovalFailed(const QString &path)
+void QFileSystemWatcherPrivate::winDriveLockForRemovalFailed(const QString &path)
{
// Windows: Request to lock a (removable/USB) drive failed (blocked by other
// application), restore the watched paths.
@@ -161,7 +154,7 @@ void QFileSystemWatcherPrivate::_q_winDriveLockForRemovalFailed(const QString &p
}
}
-void QFileSystemWatcherPrivate::_q_winDriveRemoved(const QString &path)
+void QFileSystemWatcherPrivate::winDriveRemoved(const QString &path)
{
// Windows: Drive finally removed, clear out paths stored in lock request.
if (!path.isEmpty())
diff --git a/src/corelib/io/qfilesystemwatcher.h b/src/corelib/io/qfilesystemwatcher.h
index f5400bc9d8..668bc143b2 100644
--- a/src/corelib/io/qfilesystemwatcher.h
+++ b/src/corelib/io/qfilesystemwatcher.h
@@ -34,10 +34,6 @@ public:
Q_SIGNALS:
void fileChanged(const QString &path, QPrivateSignal);
void directoryChanged(const QString &path, QPrivateSignal);
-
-private:
- Q_PRIVATE_SLOT(d_func(), void _q_fileChanged(const QString &path, bool removed))
- Q_PRIVATE_SLOT(d_func(), void _q_directoryChanged(const QString &path, bool removed))
};
QT_END_NAMESPACE
diff --git a/src/corelib/io/qfilesystemwatcher_inotify.cpp b/src/corelib/io/qfilesystemwatcher_inotify.cpp
index a37eecfebd..e60f688110 100644
--- a/src/corelib/io/qfilesystemwatcher_inotify.cpp
+++ b/src/corelib/io/qfilesystemwatcher_inotify.cpp
@@ -217,7 +217,8 @@ QInotifyFileSystemWatcherEngine::QInotifyFileSystemWatcherEngine(int fd, QObject
notifier(fd, QSocketNotifier::Read, this)
{
fcntl(inotifyFd, F_SETFD, FD_CLOEXEC);
- connect(&notifier, SIGNAL(activated(QSocketDescriptor)), SLOT(readFromInotify()));
+ QObject::connect(&notifier, &QSocketNotifier::activated,
+ this, &QInotifyFileSystemWatcherEngine::readFromInotify);
}
QInotifyFileSystemWatcherEngine::~QInotifyFileSystemWatcherEngine()
diff --git a/src/corelib/io/qfilesystemwatcher_kqueue.cpp b/src/corelib/io/qfilesystemwatcher_kqueue.cpp
index 40d4c1f150..7a9be337bf 100644
--- a/src/corelib/io/qfilesystemwatcher_kqueue.cpp
+++ b/src/corelib/io/qfilesystemwatcher_kqueue.cpp
@@ -165,7 +165,7 @@ void QKqueueFileSystemWatcherEngine::readFromKqueue()
int r;
struct kevent kev;
struct timespec ts = { 0, 0 }; // 0 ts, because we want to poll
- EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, &ts));
+ QT_EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, &ts));
if (r < 0) {
perror("QKqueueFileSystemWatcherEngine: error during kevent wait");
return;
@@ -218,3 +218,5 @@ void QKqueueFileSystemWatcherEngine::readFromKqueue()
}
QT_END_NAMESPACE
+
+#include "moc_qfilesystemwatcher_kqueue_p.cpp"
diff --git a/src/corelib/io/qfilesystemwatcher_p.h b/src/corelib/io/qfilesystemwatcher_p.h
index 34fef20704..c34e3e2408 100644
--- a/src/corelib/io/qfilesystemwatcher_p.h
+++ b/src/corelib/io/qfilesystemwatcher_p.h
@@ -69,13 +69,15 @@ public:
QStringList files, directories;
// private slots
- void _q_fileChanged(const QString &path, bool removed);
- void _q_directoryChanged(const QString &path, bool removed);
+ void fileChanged(const QString &path, bool removed);
+ void directoryChanged(const QString &path, bool removed);
+
+ void connectEngine(QFileSystemWatcherEngine *e);
#if defined(Q_OS_WIN)
- void _q_winDriveLockForRemoval(const QString &);
- void _q_winDriveLockForRemovalFailed(const QString &);
- void _q_winDriveRemoved(const QString &);
+ void winDriveLockForRemoval(const QString &);
+ void winDriveLockForRemovalFailed(const QString &);
+ void winDriveRemoved(const QString &);
private:
QHash<QChar, QStringList> temporarilyRemovedPaths;
diff --git a/src/corelib/io/qfilesystemwatcher_polling.cpp b/src/corelib/io/qfilesystemwatcher_polling.cpp
index 03425ac212..d34c8c49e8 100644
--- a/src/corelib/io/qfilesystemwatcher_polling.cpp
+++ b/src/corelib/io/qfilesystemwatcher_polling.cpp
@@ -2,16 +2,24 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qfilesystemwatcher_polling_p.h"
+
+#include <QtCore/qlatin1stringview.h>
#include <QtCore/qscopeguard.h>
#include <QtCore/qtimer.h>
+#include <chrono>
+
+using namespace std::chrono_literals;
+
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
+static constexpr auto PollingInterval = 1s;
+
QPollingFileSystemWatcherEngine::QPollingFileSystemWatcherEngine(QObject *parent)
- : QFileSystemWatcherEngine(parent),
- timer(this)
+ : QFileSystemWatcherEngine(parent)
{
- connect(&timer, SIGNAL(timeout()), SLOT(timeout()));
}
QStringList QPollingFileSystemWatcherEngine::addPaths(const QStringList &paths,
@@ -40,10 +48,17 @@ QStringList QPollingFileSystemWatcherEngine::addPaths(const QStringList &paths,
sg.dismiss();
}
+ std::chrono::milliseconds interval = PollingInterval;
+#ifdef QT_BUILD_INTERNAL
+ if (Q_UNLIKELY(parent()->objectName().startsWith("_qt_autotest_force_engine_"_L1))) {
+ interval = 10ms; // Special case to speed up the unittests
+ }
+#endif
+
if ((!this->files.isEmpty() ||
!this->directories.isEmpty()) &&
!timer.isActive()) {
- timer.start(PollingInterval);
+ timer.start(interval, this);
}
return unhandled;
@@ -72,8 +87,11 @@ QStringList QPollingFileSystemWatcherEngine::removePaths(const QStringList &path
return unhandled;
}
-void QPollingFileSystemWatcherEngine::timeout()
+void QPollingFileSystemWatcherEngine::timerEvent(QTimerEvent *e)
{
+ if (e->timerId() != timer.timerId())
+ return QFileSystemWatcherEngine::timerEvent(e);
+
for (auto it = files.begin(), end = files.end(); it != end; /*erasing*/) {
QString path = it.key();
QFileInfo fi(path);
diff --git a/src/corelib/io/qfilesystemwatcher_polling_p.h b/src/corelib/io/qfilesystemwatcher_polling_p.h
index 2d7423b39d..b65ff05575 100644
--- a/src/corelib/io/qfilesystemwatcher_polling_p.h
+++ b/src/corelib/io/qfilesystemwatcher_polling_p.h
@@ -15,11 +15,11 @@
// We mean it.
//
+#include <QtCore/qbasictimer.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qmutex.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qdir.h>
-#include <QtCore/qtimer.h>
#include <QtCore/qhash.h>
#include "qfilesystemwatcher_p.h"
@@ -27,8 +27,6 @@
QT_REQUIRE_CONFIG(filesystemwatcher);
QT_BEGIN_NAMESPACE
-enum { PollingInterval = 1000 };
-
class QPollingFileSystemWatcherEngine : public QFileSystemWatcherEngine
{
Q_OBJECT
@@ -77,11 +75,11 @@ public:
QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories) override;
QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories) override;
-private Q_SLOTS:
- void timeout();
+private:
+ void timerEvent(QTimerEvent *) final;
private:
- QTimer timer;
+ QBasicTimer timer;
};
QT_END_NAMESPACE
diff --git a/src/corelib/io/qfilesystemwatcher_win.cpp b/src/corelib/io/qfilesystemwatcher_win.cpp
index a0b2b006d6..5418265ba2 100644
--- a/src/corelib/io/qfilesystemwatcher_win.cpp
+++ b/src/corelib/io/qfilesystemwatcher_win.cpp
@@ -708,3 +708,4 @@ void QWindowsFileSystemWatcherEngineThread::wakeup()
QT_END_NAMESPACE
# include "qfilesystemwatcher_win.moc"
+# include "moc_qfilesystemwatcher_win_p.cpp"
diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp
index 0ff7da1ac4..f49106edd4 100644
--- a/src/corelib/io/qfsfileengine.cpp
+++ b/src/corelib/io/qfsfileengine.cpp
@@ -6,7 +6,6 @@
#include "qfsfileengine_iterator_p.h"
#include "qfilesystemengine_p.h"
#include "qdatetime.h"
-#include "qdiriterator.h"
#include "qset.h"
#include <QtCore/qdebug.h>
@@ -273,7 +272,7 @@ bool QFSFileEnginePrivate::openFh(QIODevice::OpenMode openMode, FILE *fh)
if (ret != 0) {
q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError,
- QSystemError::stdString());
+ QSystemError::stdString(errno));
this->openMode = QIODevice::NotOpen;
this->fh = nullptr;
@@ -335,7 +334,7 @@ bool QFSFileEnginePrivate::openFd(QIODevice::OpenMode openMode, int fd)
if (ret == -1) {
q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError,
- QSystemError::stdString());
+ QSystemError::stdString(errno));
this->openMode = QIODevice::NotOpen;
this->fd = -1;
@@ -394,7 +393,7 @@ bool QFSFileEnginePrivate::closeFdFh()
if (!flushed || !closed) {
if (flushed) {
// If not flushed, we want the flush error to fall through.
- q->setError(QFile::UnspecifiedError, QSystemError::stdString());
+ q->setError(QFile::UnspecifiedError, QSystemError::stdString(errno));
}
return false;
}
@@ -446,7 +445,7 @@ bool QFSFileEnginePrivate::flushFh()
if (ret != 0) {
q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError,
- QSystemError::stdString());
+ QSystemError::stdString(errno));
return false;
}
return true;
@@ -521,11 +520,11 @@ bool QFSFileEngine::seek(qint64 pos)
/*!
\reimp
*/
-QDateTime QFSFileEngine::fileTime(FileTime time) const
+QDateTime QFSFileEngine::fileTime(QFile::FileTime time) const
{
Q_D(const QFSFileEngine);
- if (time == AccessTime) {
+ if (time == QFile::FileAccessTime) {
// always refresh for the access time
d->metaData.clearFlags(QFileSystemMetaData::AccessTime);
}
@@ -561,14 +560,14 @@ bool QFSFileEnginePrivate::seekFdFh(qint64 pos)
} while (ret != 0 && errno == EINTR);
if (ret != 0) {
- q->setError(QFile::ReadError, QSystemError::stdString());
+ q->setError(QFile::ReadError, QSystemError::stdString(errno));
return false;
}
} else {
// Unbuffered stdio mode.
if (QT_LSEEK(fd, QT_OFF_T(pos), SEEK_SET) == -1) {
+ q->setError(QFile::PositionError, QSystemError::stdString(errno));
qWarning("QFile::at: Cannot set file position %lld", pos);
- q->setError(QFile::PositionError, QSystemError::stdString());
return false;
}
}
@@ -621,17 +620,15 @@ qint64 QFSFileEnginePrivate::readFdFh(char *data, qint64 len)
// Buffered stdlib mode.
size_t result;
- bool retry = true;
do {
result = fread(data + readBytes, 1, size_t(len - readBytes), fh);
- eof = feof(fh);
- if (retry && eof && result == 0) {
+ eof = feof(fh); // Doesn't change errno
+ if (eof && result == 0) {
// On OS X, this is needed, e.g., if a file was written to
// through another stream since our last read. See test
// tst_QFile::appendAndRead
QT_FSEEK(fh, QT_FTELL(fh), SEEK_SET); // re-sync stream.
- retry = false;
- continue;
+ break;
}
readBytes += result;
} while (!eof && (result == 0 ? errno == EINTR : readBytes < len));
@@ -651,12 +648,13 @@ qint64 QFSFileEnginePrivate::readFdFh(char *data, qint64 len)
result = QT_READ(fd, data + readBytes, chunkSize);
} while (result > 0 && (readBytes += result) < len);
- eof = !(result == -1);
+ // QT_READ (::read()) returns 0 to indicate end-of-file
+ eof = result == 0;
}
if (!eof && readBytes == 0) {
readBytes = -1;
- q->setError(QFile::ReadError, QSystemError::stdString());
+ q->setError(QFile::ReadError, QSystemError::stdString(errno));
}
return readBytes;
@@ -701,8 +699,8 @@ qint64 QFSFileEnginePrivate::readLineFdFh(char *data, qint64 maxlen)
// does the same, so we'd get two '\0' at the end - passing maxlen + 1
// solves this.
if (!fgets(data, int(maxlen + 1), fh)) {
- if (!feof(fh))
- q->setError(QFile::ReadError, QSystemError::stdString());
+ if (!feof(fh)) // Doesn't change errno
+ q->setError(QFile::ReadError, QSystemError::stdString(errno));
return -1; // error
}
@@ -779,7 +777,8 @@ qint64 QFSFileEnginePrivate::writeFdFh(const char *data, qint64 len)
if (len && writtenBytes == 0) {
writtenBytes = -1;
- q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError, QSystemError::stdString());
+ q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError,
+ QSystemError::stdString(errno));
} else {
// reset the cached size, if any
metaData.clearFlags(QFileSystemMetaData::SizeAttribute);
@@ -792,18 +791,13 @@ qint64 QFSFileEnginePrivate::writeFdFh(const char *data, qint64 len)
/*!
\internal
*/
-QAbstractFileEngine::Iterator *QFSFileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames)
+QAbstractFileEngine::IteratorUniquePtr
+QFSFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
{
- return new QFSFileEngineIterator(filters, filterNames);
+ return std::make_unique<QFSFileEngineIterator>(path, filters, filterNames);
}
-/*!
- \internal
-*/
-QAbstractFileEngine::Iterator *QFSFileEngine::endEntryList()
-{
- return nullptr;
-}
#endif // QT_NO_FILESYSTEMITERATOR
/*!
@@ -906,7 +900,7 @@ bool QFSFileEngine::supportsExtension(Extension extension) const
\reimp
*/
-/*! \fn bool QFSFileEngine::setFileTime(const QDateTime &newDate, QAbstractFileEngine::FileTime time)
+/*! \fn bool QFSFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
\reimp
*/
@@ -996,29 +990,32 @@ bool QFSFileEngine::remove()
return ret;
}
-/*!
- \reimp
+/*
+ An alternative to setFileName() when you have already constructed
+ a QFileSystemEntry.
*/
-bool QFSFileEngine::rename(const QString &newName)
+void QFSFileEngine::setFileEntry(QFileSystemEntry &&entry)
{
Q_D(QFSFileEngine);
- QSystemError error;
- bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error);
- if (!ret)
- setError(QFile::RenameError, error.toString());
- return ret;
+ d->init();
+ d->fileEntry = std::move(entry);
}
-/*!
- \reimp
-*/
-bool QFSFileEngine::renameOverwrite(const QString &newName)
+
+bool QFSFileEngine::rename_helper(const QString &newName, RenameMode mode)
{
Q_D(QFSFileEngine);
+
+ auto func = mode == Rename ? QFileSystemEngine::renameFile
+ : QFileSystemEngine::renameOverwriteFile;
QSystemError error;
- bool ret = QFileSystemEngine::renameOverwriteFile(d->fileEntry, QFileSystemEntry(newName), error);
- if (!ret)
+ auto newEntry = QFileSystemEntry(newName);
+ const bool ret = func(d->fileEntry, newEntry, error);
+ if (!ret) {
setError(QFile::RenameError, error.toString());
- return ret;
+ return false;
+ }
+ setFileEntry(std::move(newEntry));
+ return true;
}
/*!
diff --git a/src/corelib/io/qfsfileengine_iterator.cpp b/src/corelib/io/qfsfileengine_iterator.cpp
index 7f43579a8f..fb0332f7f3 100644
--- a/src/corelib/io/qfsfileengine_iterator.cpp
+++ b/src/corelib/io/qfsfileengine_iterator.cpp
@@ -9,9 +9,10 @@
QT_BEGIN_NAMESPACE
-QFSFileEngineIterator::QFSFileEngineIterator(QDir::Filters filters, const QStringList &filterNames)
- : QAbstractFileEngineIterator(filters, filterNames)
- , done(false)
+QFSFileEngineIterator::QFSFileEngineIterator(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
+ : QAbstractFileEngineIterator(path, filters, filterNames),
+ nativeIterator(new QFileSystemIterator(QFileSystemEntry(path), filters))
{
}
@@ -19,48 +20,30 @@ QFSFileEngineIterator::~QFSFileEngineIterator()
{
}
-bool QFSFileEngineIterator::hasNext() const
+bool QFSFileEngineIterator::advance()
{
- if (!done && !nativeIterator) {
- nativeIterator.reset(new QFileSystemIterator(QFileSystemEntry(path()),
- filters(), nameFilters()));
- advance();
- }
-
- return !done;
-}
-
-QString QFSFileEngineIterator::next()
-{
- if (!hasNext())
- return QString();
-
- advance();
- return currentFilePath();
-}
-
-void QFSFileEngineIterator::advance() const
-{
- currentInfo = nextInfo;
+ if (!nativeIterator)
+ return false;
QFileSystemEntry entry;
QFileSystemMetaData data;
if (nativeIterator->advance(entry, data)) {
- nextInfo = QFileInfo(new QFileInfoPrivate(entry, data));
+ m_fileInfo = QFileInfo(new QFileInfoPrivate(entry, data));
+ return true;
} else {
- done = true;
nativeIterator.reset();
+ return false;
}
}
QString QFSFileEngineIterator::currentFileName() const
{
- return currentInfo.fileName();
+ return m_fileInfo.fileName();
}
QFileInfo QFSFileEngineIterator::currentFileInfo() const
{
- return currentInfo;
+ return m_fileInfo;
}
QT_END_NAMESPACE
diff --git a/src/corelib/io/qfsfileengine_iterator_p.h b/src/corelib/io/qfsfileengine_iterator_p.h
index 8281609ff2..b91c5d9973 100644
--- a/src/corelib/io/qfsfileengine_iterator_p.h
+++ b/src/corelib/io/qfsfileengine_iterator_p.h
@@ -23,27 +23,19 @@
QT_BEGIN_NAMESPACE
-class QFSFileEngineIteratorPrivate;
-class QFSFileEngineIteratorPlatformSpecificData;
-
class QFSFileEngineIterator : public QAbstractFileEngineIterator
{
public:
- QFSFileEngineIterator(QDir::Filters filters, const QStringList &filterNames);
+ QFSFileEngineIterator(const QString &path, QDir::Filters filters, const QStringList &filterNames);
~QFSFileEngineIterator();
- QString next() override;
- bool hasNext() const override;
+ bool advance() override;
QString currentFileName() const override;
QFileInfo currentFileInfo() const override;
private:
- void advance() const;
mutable QScopedPointer<QFileSystemIterator> nativeIterator;
- mutable QFileInfo currentInfo;
- mutable QFileInfo nextInfo;
- mutable bool done;
};
QT_END_NAMESPACE
diff --git a/src/corelib/io/qfsfileengine_p.h b/src/corelib/io/qfsfileengine_p.h
index 3cc58ea993..dfc40e20b6 100644
--- a/src/corelib/io/qfsfileengine_p.h
+++ b/src/corelib/io/qfsfileengine_p.h
@@ -60,8 +60,12 @@ public:
bool isSequential() const override;
bool remove() override;
bool copy(const QString &newName) override;
- bool rename(const QString &newName) override;
- bool renameOverwrite(const QString &newName) override;
+
+ bool rename(const QString &newName) override
+ { return rename_helper(newName, Rename); }
+ bool renameOverwrite(const QString &newName) override
+ { return rename_helper(newName, RenameOverwrite); }
+
bool link(const QString &newName) override;
bool mkdir(const QString &dirName, bool createParentDirectories,
std::optional<QFile::Permissions> permissions) const override;
@@ -76,14 +80,15 @@ public:
QString fileName(FileName file) const override;
uint ownerId(FileOwner) const override;
QString owner(FileOwner) const override;
- bool setFileTime(const QDateTime &newDate, FileTime time) override;
- QDateTime fileTime(FileTime time) const override;
+ bool setFileTime(const QDateTime &newDate, QFile::FileTime time) override;
+ QDateTime fileTime(QFile::FileTime time) const override;
void setFileName(const QString &file) override;
+ void setFileEntry(QFileSystemEntry &&entry);
int handle() const override;
#ifndef QT_NO_FILESYSTEMITERATOR
- Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
- Iterator *endEntryList() override;
+ IteratorUniquePtr beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames) override;
#endif
qint64 read(char *data, qint64 maxlen) override;
@@ -110,6 +115,10 @@ public:
protected:
QFSFileEngine(QFSFileEnginePrivate &dd);
+
+private:
+ enum RenameMode : int { Rename, RenameOverwrite };
+ bool rename_helper(const QString &newName, RenameMode mode);
};
class Q_AUTOTEST_EXPORT QFSFileEnginePrivate : public QAbstractFileEnginePrivate
@@ -151,6 +160,9 @@ public:
#ifndef Q_OS_WIN
bool isSequentialFdFh() const;
#endif
+#ifdef Q_OS_WIN
+ bool nativeRenameOverwrite(const QFileSystemEntry &newEntry);
+#endif
uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
bool unmap(uchar *ptr);
diff --git a/src/corelib/io/qfsfileengine_unix.cpp b/src/corelib/io/qfsfileengine_unix.cpp
index 217474cb7f..5806689182 100644
--- a/src/corelib/io/qfsfileengine_unix.cpp
+++ b/src/corelib/io/qfsfileengine_unix.cpp
@@ -160,9 +160,9 @@ bool QFSFileEnginePrivate::nativeSyncToDisk()
Q_Q(QFSFileEngine);
int ret;
#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
- EINTR_LOOP(ret, fdatasync(nativeHandle()));
+ QT_EINTR_LOOP(ret, fdatasync(nativeHandle()));
#else
- EINTR_LOOP(ret, fsync(nativeHandle()));
+ QT_EINTR_LOOP(ret, fsync(nativeHandle()));
#endif
if (ret != 0)
q->setError(QFile::WriteError, qt_error_string(errno));
@@ -525,7 +525,7 @@ bool QFSFileEngine::setSize(qint64 size)
return ret;
}
-bool QFSFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
+bool QFSFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
{
Q_D(QFSFileEngine);
@@ -565,11 +565,11 @@ uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFla
}
if (offset < 0 || offset > maxFileOffset
- || size < 0 || quint64(size) > quint64(size_t(-1))) {
+ || size <= 0
+ || quint64(size) > quint64(size_t(-1))) {
q->setError(QFile::UnspecifiedError, qt_error_string(EINVAL));
return nullptr;
}
-
// If we know the mapping will extend beyond EOF, fail early to avoid
// undefined behavior. Otherwise, let mmap have its say.
if (doStat(QFileSystemMetaData::SizeAttribute)
diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp
index daa4326212..4ac305f49b 100644
--- a/src/corelib/io/qfsfileengine_win.cpp
+++ b/src/corelib/io/qfsfileengine_win.cpp
@@ -27,6 +27,8 @@
#define SECURITY_WIN32
#include <security.h>
+#include <memory>
+
#ifndef PATH_MAX
#define PATH_MAX FILENAME_MAX
#endif
@@ -394,6 +396,35 @@ bool QFSFileEnginePrivate::nativeIsSequential() const
|| (fileType == FILE_TYPE_PIPE);
}
+bool QFSFileEnginePrivate::nativeRenameOverwrite(const QFileSystemEntry &newEntry)
+{
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ return false;
+ const QString newFilePath = newEntry.nativeFilePath();
+ const size_t nameByteLength = newFilePath.length() * sizeof(wchar_t);
+ if (nameByteLength + sizeof(wchar_t) > std::numeric_limits<DWORD>::max())
+ return false;
+
+ constexpr size_t RenameInfoSize = sizeof(FILE_RENAME_INFO);
+ const size_t renameDataSize = RenameInfoSize + nameByteLength + sizeof(wchar_t);
+ QVarLengthArray<char> v(qsizetype(renameDataSize), 0);
+
+ auto *renameInfo = q20::construct_at(reinterpret_cast<FILE_RENAME_INFO *>(v.data()));
+ auto renameInfoRAII = qScopeGuard([&] { std::destroy_at(renameInfo); });
+ renameInfo->ReplaceIfExists = TRUE;
+ renameInfo->RootDirectory = nullptr;
+ renameInfo->FileNameLength = DWORD(nameByteLength);
+ memcpy(renameInfo->FileName, newFilePath.data(), nameByteLength);
+
+ bool res = SetFileInformationByHandle(fileHandle, FileRenameInfo, renameInfo,
+ DWORD(renameDataSize));
+ if (!res) {
+ DWORD error = GetLastError();
+ q_func()->setError(QFile::RenameError, qt_error_string(int(error)));
+ }
+ return res;
+}
+
bool QFSFileEngine::caseSensitive() const
{
return false;
@@ -706,7 +737,7 @@ bool QFSFileEngine::setSize(qint64 size)
return false;
}
-bool QFSFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
+bool QFSFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
{
Q_D(QFSFileEngine);
@@ -715,7 +746,7 @@ bool QFSFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
return false;
}
- if (!newDate.isValid() || time == QAbstractFileEngine::MetadataChangeTime) {
+ if (!newDate.isValid() || time == QFile::FileMetadataChangeTime) {
setError(QFile::UnspecifiedError, qt_error_string(ERROR_INVALID_PARAMETER));
return false;
}
diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp
index 81c2bc1695..b0029e2af7 100644
--- a/src/corelib/io/qiodevice.cpp
+++ b/src/corelib/io/qiodevice.cpp
@@ -9,7 +9,6 @@
#include "qfile.h"
#include "qstringlist.h"
#include "qdir.h"
-#include "private/qbytearray_p.h"
#include "private/qtools_p.h"
#include <algorithm>
@@ -45,6 +44,7 @@ static void debugBinaryString(const char *input, qint64 maxlen)
#define Q_VOID
+Q_DECL_COLD_FUNCTION
static void checkWarnMessage(const QIODevice *device, const char *function, const char *what)
{
#ifndef QT_NO_WARNING_OUTPUT
@@ -88,9 +88,9 @@ static void checkWarnMessage(const QIODevice *device, const char *function, cons
#define CHECK_MAXBYTEARRAYSIZE(function) \
do { \
- if (maxSize >= MaxByteArraySize) { \
+ if (maxSize >= QByteArray::max_size()) { \
checkWarnMessage(this, #function, "maxSize argument exceeds QByteArray size limit"); \
- maxSize = MaxByteArraySize - 1; \
+ maxSize = QByteArray::max_size() - 1; \
} \
} while (0)
@@ -1242,7 +1242,7 @@ QByteArray QIODevice::readAll()
: d->buffer.size());
qint64 readResult;
do {
- if (readBytes + readChunkSize >= MaxByteArraySize) {
+ if (readBytes + readChunkSize >= QByteArray::max_size()) {
// If resize would fail, don't read more, return what we have.
break;
}
@@ -1256,8 +1256,8 @@ QByteArray QIODevice::readAll()
} else {
// Read it all in one go.
readBytes -= d->pos;
- if (readBytes >= MaxByteArraySize)
- readBytes = MaxByteArraySize;
+ if (readBytes >= QByteArray::max_size())
+ readBytes = QByteArray::max_size();
result.resize(readBytes);
readBytes = d->read(result.data(), readBytes);
}
@@ -1448,7 +1448,7 @@ QByteArray QIODevice::readLine(qint64 maxSize)
qint64 readBytes = 0;
if (maxSize == 0) {
// Size is unknown, read incrementally.
- maxSize = MaxByteArraySize - 1;
+ maxSize = QByteArray::max_size() - 1;
// The first iteration needs to leave an extra byte for the terminating null
result.resize(1);
diff --git a/src/corelib/io/qlockfile.cpp b/src/corelib/io/qlockfile.cpp
index c90d2886c2..0eb6acb694 100644
--- a/src/corelib/io/qlockfile.cpp
+++ b/src/corelib/io/qlockfile.cpp
@@ -257,7 +257,7 @@ bool QLockFile::tryLock(int timeout)
return tryLock(std::chrono::milliseconds{ timeout });
}
-/*! \fn bool QLockFile::tryLock(std::chrono::milliseconds timeout)
+/*!
\overload
\since 6.2
@@ -275,11 +275,7 @@ bool QLockFile::tryLock(int timeout)
\sa lock(), unlock()
*/
-#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
bool QLockFile::tryLock(std::chrono::milliseconds timeout)
-#else
-bool QLockFile::tryLock_impl(std::chrono::milliseconds timeout)
-#endif
{
using namespace std::chrono_literals;
using Msec = std::chrono::milliseconds;
diff --git a/src/corelib/io/qlockfile.h b/src/corelib/io/qlockfile.h
index ecc26d137f..af481ab59b 100644
--- a/src/corelib/io/qlockfile.h
+++ b/src/corelib/io/qlockfile.h
@@ -28,14 +28,7 @@ public:
void setStaleLockTime(int);
int staleLockTime() const;
-#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
bool tryLock(std::chrono::milliseconds timeout = std::chrono::milliseconds::zero());
-#else
- bool tryLock(std::chrono::milliseconds timeout = std::chrono::milliseconds::zero())
- {
- return tryLock_impl(timeout);
- }
-#endif
void setStaleLockTime(std::chrono::milliseconds value);
std::chrono::milliseconds staleLockTimeAsDuration() const;
@@ -58,10 +51,6 @@ protected:
private:
Q_DECLARE_PRIVATE(QLockFile)
Q_DISABLE_COPY(QLockFile)
-
-#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
- bool tryLock_impl(std::chrono::milliseconds timeout);
-#endif
};
QT_END_NAMESPACE
diff --git a/src/corelib/io/qloggingcategory.h b/src/corelib/io/qloggingcategory.h
index 5e0082e2aa..7c32beea1a 100644
--- a/src/corelib/io/qloggingcategory.h
+++ b/src/corelib/io/qloggingcategory.h
@@ -67,11 +67,6 @@ template <QtMsgType Which> struct QLoggingCategoryMacroHolder
if (IsOutputEnabled)
init(cat);
}
- explicit QLoggingCategoryMacroHolder(QMessageLogger::CategoryFunction catfunc)
- {
- if (IsOutputEnabled)
- init(catfunc());
- }
void init(const QLoggingCategory &cat) noexcept
{
category = &cat;
@@ -122,7 +117,7 @@ template <> const bool QLoggingCategoryMacroHolder<QtWarningMsg>::IsOutputEnable
}
#define QT_MESSAGE_LOGGER_COMMON(category, level) \
- for (QLoggingCategoryMacroHolder<level> qt_category(category); qt_category; qt_category.control = false) \
+ for (QLoggingCategoryMacroHolder<level> qt_category((category)()); qt_category; qt_category.control = false) \
QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, qt_category.name())
#define qCDebug(category, ...) QT_MESSAGE_LOGGER_COMMON(category, QtDebugMsg).debug(__VA_ARGS__)
diff --git a/src/corelib/io/qloggingregistry.cpp b/src/corelib/io/qloggingregistry.cpp
index 3c835cce5a..b4181a2fa6 100644
--- a/src/corelib/io/qloggingregistry.cpp
+++ b/src/corelib/io/qloggingregistry.cpp
@@ -32,8 +32,7 @@ Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry)
\internal
Constructs a logging rule with default values.
*/
-QLoggingRule::QLoggingRule() :
- enabled(false)
+QLoggingRule::QLoggingRule()
{
}
@@ -41,9 +40,7 @@ QLoggingRule::QLoggingRule() :
\internal
Constructs a logging rule.
*/
-QLoggingRule::QLoggingRule(QStringView pattern, bool enabled) :
- messageType(-1),
- enabled(enabled)
+QLoggingRule::QLoggingRule(QStringView pattern, bool enabled) : enabled(enabled)
{
parse(pattern);
}
@@ -244,20 +241,29 @@ QLoggingRegistry::QLoggingRegistry()
static bool qtLoggingDebug()
{
- static const bool debugEnv = qEnvironmentVariableIsSet("QT_LOGGING_DEBUG");
+ static const bool debugEnv = [] {
+ bool debug = qEnvironmentVariableIsSet("QT_LOGGING_DEBUG");
+ if (debug)
+ debugMsg("QT_LOGGING_DEBUG environment variable is set.");
+ return debug;
+ }();
return debugEnv;
}
static QList<QLoggingRule> loadRulesFromFile(const QString &filePath)
{
+ if (qtLoggingDebug()) {
+ debugMsg("Checking \"%s\" for rules",
+ QDir::toNativeSeparators(filePath).toUtf8().constData());
+ }
+
QFile file(filePath);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
- if (qtLoggingDebug())
- debugMsg("Loading \"%s\" ...",
- QDir::toNativeSeparators(file.fileName()).toUtf8().constData());
QTextStream stream(&file);
QLoggingSettingsParser parser;
parser.setContent(stream);
+ if (qtLoggingDebug())
+ debugMsg("Loaded %td rules", static_cast<ptrdiff_t>(parser.rules().size()));
return parser.rules();
}
return QList<QLoggingRule>();
@@ -270,19 +276,30 @@ static QList<QLoggingRule> loadRulesFromFile(const QString &filePath)
*/
void QLoggingRegistry::initializeRules()
{
+ if (qtLoggingDebug()) {
+ debugMsg("Initializing the rules database ...");
+ debugMsg("Checking %s environment variable", "QTLOGGING_CONF");
+ }
QList<QLoggingRule> er, qr, cr;
// get rules from environment
const QByteArray rulesFilePath = qgetenv("QT_LOGGING_CONF");
if (!rulesFilePath.isEmpty())
er = loadRulesFromFile(QFile::decodeName(rulesFilePath));
+ if (qtLoggingDebug())
+ debugMsg("Checking %s environment variable", "QT_LOGGING_RULES");
+
const QByteArray rulesSrc = qgetenv("QT_LOGGING_RULES").replace(';', '\n');
if (!rulesSrc.isEmpty()) {
- QTextStream stream(rulesSrc);
- QLoggingSettingsParser parser;
- parser.setImplicitRulesSection(true);
- parser.setContent(stream);
- er += parser.rules();
+ QTextStream stream(rulesSrc);
+ QLoggingSettingsParser parser;
+ parser.setImplicitRulesSection(true);
+ parser.setContent(stream);
+
+ if (qtLoggingDebug())
+ debugMsg("Loaded %td rules", static_cast<ptrdiff_t>(parser.rules().size()));
+
+ er += parser.rules();
}
const QString configFileName = QStringLiteral("qtlogging.ini");
@@ -347,10 +364,10 @@ void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
for enabling debugging by default for category \a categoryName. The
category name must start with "qt."
*/
-void QLoggingRegistry::registerEnvironmentOverrideForCategory(QByteArrayView categoryName,
- QByteArrayView environment)
+void QLoggingRegistry::registerEnvironmentOverrideForCategory(const char *categoryName,
+ const char *environment)
{
- qtCategoryEnvironmentOverrides.insert(categoryName, environment);
+ qtCategoryEnvironmentOverrides.insert_or_assign(categoryName, environment);
}
/*!
@@ -442,7 +459,7 @@ void QLoggingRegistry::defaultCategoryFilter(QLoggingCategory *cat)
if (it == reg->qtCategoryEnvironmentOverrides.end())
debug = false;
else
- debug = qEnvironmentVariableIntValue(it.value().data());
+ debug = qEnvironmentVariableIntValue(it->second);
}
}
diff --git a/src/corelib/io/qloggingregistry_p.h b/src/corelib/io/qloggingregistry_p.h
index 4f18f6adf4..92b96f0ba4 100644
--- a/src/corelib/io/qloggingregistry_p.h
+++ b/src/corelib/io/qloggingregistry_p.h
@@ -19,11 +19,12 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qlist.h>
#include <QtCore/qhash.h>
-#include <QtCore/qmap.h>
#include <QtCore/qmutex.h>
#include <QtCore/qstring.h>
#include <QtCore/qtextstream.h>
+#include <map>
+
class tst_QLoggingRegistry;
QT_BEGIN_NAMESPACE
@@ -54,9 +55,9 @@ public:
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
QString category;
- int messageType;
+ int messageType = -1;
PatternFlags flags;
- bool enabled;
+ bool enabled = false;
private:
void parse(QStringView pattern);
@@ -85,6 +86,7 @@ private:
class Q_AUTOTEST_EXPORT QLoggingRegistry
{
+ Q_DISABLE_COPY_MOVE(QLoggingRegistry)
public:
QLoggingRegistry();
@@ -96,7 +98,7 @@ public:
#ifndef QT_BUILD_INTERNAL
Q_CORE_EXPORT // always export from QtCore
#endif
- void registerEnvironmentOverrideForCategory(QByteArrayView categoryName, QByteArrayView environment);
+ void registerEnvironmentOverrideForCategory(const char *categoryName, const char *environment);
void setApiRules(const QString &content);
@@ -126,7 +128,7 @@ private:
QList<QLoggingRule> ruleSets[NumRuleSets];
QHash<QLoggingCategory *, QtMsgType> categories;
QLoggingCategory::CategoryFilter categoryFilter;
- QMap<QByteArrayView, QByteArrayView> qtCategoryEnvironmentOverrides;
+ std::map<QByteArrayView, const char *> qtCategoryEnvironmentOverrides;
friend class ::tst_QLoggingRegistry;
};
@@ -139,12 +141,12 @@ public:
{}
private:
- static const char *registerOverride(QByteArrayView categoryName, QByteArrayView environment)
+ static const char *registerOverride(const char *categoryName, const char *environment)
{
QLoggingRegistry *c = QLoggingRegistry::instance();
if (c)
c->registerEnvironmentOverrideForCategory(categoryName, environment);
- return categoryName.data();
+ return categoryName;
}
};
diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp
index 87fa6f02bd..260fea7969 100644
--- a/src/corelib/io/qnoncontiguousbytedevice.cpp
+++ b/src/corelib/io/qnoncontiguousbytedevice.cpp
@@ -100,14 +100,18 @@ QNonContiguousByteDevice::~QNonContiguousByteDevice()
}
// FIXME we should scrap this whole implementation and instead change the ByteArrayImpl to be able to cope with sub-arrays?
-QNonContiguousByteDeviceBufferImpl::QNonContiguousByteDeviceBufferImpl(QBuffer *b) : QNonContiguousByteDevice()
+QNonContiguousByteDeviceBufferImpl::QNonContiguousByteDeviceBufferImpl(QBuffer *b)
+ : QNonContiguousByteDevice(),
+ buffer(b),
+ byteArray(QByteArray::fromRawData(buffer->buffer().constData() + buffer->pos(),
+ buffer->size() - buffer->pos())),
+ arrayImpl(new QNonContiguousByteDeviceByteArrayImpl(&byteArray))
{
- buffer = b;
- byteArray = QByteArray::fromRawData(buffer->buffer().constData() + buffer->pos(), buffer->size() - buffer->pos());
- arrayImpl = new QNonContiguousByteDeviceByteArrayImpl(&byteArray);
arrayImpl->setParent(this);
- connect(arrayImpl, SIGNAL(readyRead()), SIGNAL(readyRead()));
- connect(arrayImpl, SIGNAL(readProgress(qint64,qint64)), SIGNAL(readProgress(qint64,qint64)));
+ connect(arrayImpl, &QNonContiguousByteDevice::readyRead, this,
+ &QNonContiguousByteDevice::readyRead);
+ connect(arrayImpl, &QNonContiguousByteDevice::readProgress, this,
+ &QNonContiguousByteDevice::readProgress);
}
QNonContiguousByteDeviceBufferImpl::~QNonContiguousByteDeviceBufferImpl()
@@ -139,9 +143,9 @@ qint64 QNonContiguousByteDeviceBufferImpl::size() const
return arrayImpl->size();
}
-QNonContiguousByteDeviceByteArrayImpl::QNonContiguousByteDeviceByteArrayImpl(QByteArray *ba) : QNonContiguousByteDevice(), currentPosition(0)
+QNonContiguousByteDeviceByteArrayImpl::QNonContiguousByteDeviceByteArrayImpl(QByteArray *ba)
+ : QNonContiguousByteDevice(), byteArray(ba), currentPosition(0)
{
- byteArray = ba;
}
QNonContiguousByteDeviceByteArrayImpl::~QNonContiguousByteDeviceByteArrayImpl()
@@ -245,14 +249,19 @@ qint64 QNonContiguousByteDeviceRingBufferImpl::size() const
QNonContiguousByteDeviceIoDeviceImpl::QNonContiguousByteDeviceIoDeviceImpl(QIODevice *d)
: QNonContiguousByteDevice(),
- currentReadBuffer(nullptr), currentReadBufferSize(16*1024),
- currentReadBufferAmount(0), currentReadBufferPosition(0), totalAdvancements(0),
- eof(false)
+ device(d),
+ currentReadBuffer(nullptr),
+ currentReadBufferSize(16 * 1024),
+ currentReadBufferAmount(0),
+ currentReadBufferPosition(0),
+ totalAdvancements(0),
+ eof(false),
+ initialPosition(d->pos())
{
- device = d;
- initialPosition = d->pos();
- connect(device, SIGNAL(readyRead()), this, SIGNAL(readyRead()), Qt::QueuedConnection);
- connect(device, SIGNAL(readChannelFinished()), this, SIGNAL(readyRead()), Qt::QueuedConnection);
+ connect(device, &QIODevice::readyRead, this,
+ &QNonContiguousByteDevice::readyRead);
+ connect(device, &QIODevice::readChannelFinished, this,
+ &QNonContiguousByteDevice::readyRead);
}
QNonContiguousByteDeviceIoDeviceImpl::~QNonContiguousByteDeviceIoDeviceImpl()
@@ -262,7 +271,7 @@ QNonContiguousByteDeviceIoDeviceImpl::~QNonContiguousByteDeviceIoDeviceImpl()
const char *QNonContiguousByteDeviceIoDeviceImpl::readPointer(qint64 maximumLength, qint64 &len)
{
- if (eof == true) {
+ if (eof) {
len = -1;
return nullptr;
}
@@ -278,7 +287,8 @@ const char *QNonContiguousByteDeviceIoDeviceImpl::readPointer(qint64 maximumLeng
return currentReadBuffer->data() + currentReadBufferPosition;
}
- qint64 haveRead = device->read(currentReadBuffer->data(), qMin(maximumLength, currentReadBufferSize));
+ qint64 haveRead = device->read(currentReadBuffer->data(),
+ qMin(maximumLength, currentReadBufferSize));
if ((haveRead == -1) || (haveRead == 0 && device->atEnd() && !device->isSequential())) {
eof = true;
@@ -312,7 +322,7 @@ bool QNonContiguousByteDeviceIoDeviceImpl::advanceReadPointer(qint64 amount)
if (currentReadBufferPosition > currentReadBufferAmount) {
qint64 i = currentReadBufferPosition - currentReadBufferAmount;
while (i > 0) {
- if (device->getChar(nullptr) == false) {
+ if (!device->getChar(nullptr)) {
emit readProgress(totalAdvancements - i, size());
return false; // ### FIXME handle eof
}
@@ -328,7 +338,7 @@ bool QNonContiguousByteDeviceIoDeviceImpl::advanceReadPointer(qint64 amount)
bool QNonContiguousByteDeviceIoDeviceImpl::atEnd() const
{
- return eof == true;
+ return eof;
}
bool QNonContiguousByteDeviceIoDeviceImpl::reset()
@@ -367,18 +377,16 @@ qint64 QNonContiguousByteDeviceIoDeviceImpl::pos() const
return device->pos();
}
-QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)nullptr)
+QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd)
+ : QIODevice(nullptr), byteDevice(bd)
{
- byteDevice = bd;
- connect(bd, SIGNAL(readyRead()), SIGNAL(readyRead()));
+ connect(bd, &QNonContiguousByteDevice::readyRead, this, &QIODevice::readyRead);
open(ReadOnly);
}
QByteDeviceWrappingIoDevice::~QByteDeviceWrappingIoDevice()
-{
-
-}
+ = default;
bool QByteDeviceWrappingIoDevice::isSequential() const
{
@@ -483,9 +491,10 @@ std::shared_ptr<QNonContiguousByteDevice> QNonContiguousByteDeviceFactory::creat
\internal
*/
-QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(std::shared_ptr<QRingBuffer> ringBuffer)
+QNonContiguousByteDevice *
+QNonContiguousByteDeviceFactory::create(std::shared_ptr<QRingBuffer> ringBuffer)
{
- return new QNonContiguousByteDeviceRingBufferImpl(ringBuffer);
+ return new QNonContiguousByteDeviceRingBufferImpl(std::move(ringBuffer));
}
/*!
@@ -493,7 +502,8 @@ QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(std::shared_pt
\internal
*/
-std::shared_ptr<QNonContiguousByteDevice> QNonContiguousByteDeviceFactory::createShared(std::shared_ptr<QRingBuffer> ringBuffer)
+std::shared_ptr<QNonContiguousByteDevice>
+QNonContiguousByteDeviceFactory::createShared(std::shared_ptr<QRingBuffer> ringBuffer)
{
return std::make_shared<QNonContiguousByteDeviceRingBufferImpl>(std::move(ringBuffer));
}
@@ -515,7 +525,8 @@ QNonContiguousByteDevice* QNonContiguousByteDeviceFactory::create(QByteArray *by
\internal
*/
-std::shared_ptr<QNonContiguousByteDevice> QNonContiguousByteDeviceFactory::createShared(QByteArray *byteArray)
+std::shared_ptr<QNonContiguousByteDevice>
+QNonContiguousByteDeviceFactory::createShared(QByteArray *byteArray)
{
return std::make_shared<QNonContiguousByteDeviceByteArrayImpl>(byteArray);
}
diff --git a/src/corelib/io/qnoncontiguousbytedevice_p.h b/src/corelib/io/qnoncontiguousbytedevice_p.h
index 0daadbb714..eb75034c6a 100644
--- a/src/corelib/io/qnoncontiguousbytedevice_p.h
+++ b/src/corelib/io/qnoncontiguousbytedevice_p.h
@@ -66,6 +66,7 @@ public:
class QNonContiguousByteDeviceByteArrayImpl : public QNonContiguousByteDevice
{
+ Q_OBJECT
public:
explicit QNonContiguousByteDeviceByteArrayImpl(QByteArray *ba);
~QNonContiguousByteDeviceByteArrayImpl();
@@ -83,6 +84,7 @@ protected:
class QNonContiguousByteDeviceRingBufferImpl : public QNonContiguousByteDevice
{
+ Q_OBJECT
public:
explicit QNonContiguousByteDeviceRingBufferImpl(std::shared_ptr<QRingBuffer> rb);
~QNonContiguousByteDeviceRingBufferImpl();
@@ -143,6 +145,7 @@ protected:
// ... and the reverse thing
class QByteDeviceWrappingIoDevice : public QIODevice
{
+ Q_OBJECT
public:
explicit QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd);
~QByteDeviceWrappingIoDevice();
diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp
index ff33607fa1..108ee0b7c3 100644
--- a/src/corelib/io/qprocess.cpp
+++ b/src/corelib/io/qprocess.cpp
@@ -35,6 +35,8 @@ QT_BEGIN_NAMESPACE
\reentrant
\since 4.6
+ \compares equality
+
A process's environment is composed of a set of key=value pairs known as
environment variables. The QProcessEnvironment class wraps that concept
and allows easy manipulation of those variables. It's meant to be used
@@ -184,15 +186,17 @@ QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &o
*/
/*!
- \fn bool QProcessEnvironment::operator !=(const QProcessEnvironment &other) const
+ \fn bool QProcessEnvironment::operator!=(const QProcessEnvironment &lhs, const QProcessEnvironment &rhs)
- Returns \c true if this and the \a other QProcessEnvironment objects are different.
+ Returns \c true if the process environment objects \a lhs and \a rhs are different.
\sa operator==()
*/
/*!
- Returns \c true if this and the \a other QProcessEnvironment objects are equal.
+ \fn bool QProcessEnvironment::operator==(const QProcessEnvironment &lhs, const QProcessEnvironment &rhs)
+
+ Returns \c true if the process environment objects \a lhs and \a rhs are equal.
Two QProcessEnvironment objects are considered equal if they have the same
set of key=value pairs. The comparison of keys is done case-sensitive on
@@ -200,12 +204,12 @@ QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &o
\sa operator!=(), contains()
*/
-bool QProcessEnvironment::operator==(const QProcessEnvironment &other) const
+bool comparesEqual(const QProcessEnvironment &lhs, const QProcessEnvironment &rhs)
{
- if (d == other.d)
+ if (lhs.d == rhs.d)
return true;
- return d && other.d && d->vars == other.d->vars;
+ return lhs.d && rhs.d && lhs.d->vars == rhs.d->vars;
}
/*!
@@ -816,8 +820,16 @@ void QProcessPrivate::Channel::clear()
Its members are:
\list
\li UnixProcessParameters::flags Flags, see QProcess::UnixProcessFlags
+ \li UnixProcessParameters::lowestFileDescriptorToClose The lowest file
+ descriptor to close.
\endlist
+ When the QProcess::UnixProcessFlags::CloseFileDescriptors flag is set in
+ the \c flags field, QProcess closes the application's open file descriptors
+ before executing the child process. The descriptors 0, 1, and 2 (that is,
+ \c stdin, \c stdout, and \c stderr) are left alone, along with the ones
+ numbered lower than the value of the \c lowestFileDescriptorToClose field.
+
All of the settings above can also be manually achieved by calling the
respective POSIX function from a handler set with
QProcess::setChildProcessModifier(). This structure allows QProcess to deal
@@ -830,15 +842,32 @@ void QProcessPrivate::Channel::clear()
*/
/*!
- \enum QProcess::UnixProcessFlags
+ \enum QProcess::UnixProcessFlag
\since 6.6
These flags can be used in the \c flags field of \l UnixProcessParameters.
- \value CloseNonStandardFileDescriptors Close all file descriptors besides
- \c stdin, \c stdout, and \c stderr, preventing any currently open
- descriptor in the parent process from accidentally leaking to the
- child.
+ \value CloseFileDescriptors Close all file descriptors above the threshold
+ defined by \c lowestFileDescriptorToClose, preventing any currently
+ open descriptor in the parent process from accidentally leaking to the
+ child. The \c stdin, \c stdout, and \c stderr file descriptors are
+ never closed.
+
+ \value [since 6.7] CreateNewSession Starts a new process session, by calling
+ \c{setsid(2)}. This allows the child process to outlive the session
+ the current process is in. This is one of the steps that
+ startDetached() takes to allow the process to detach, and is also one
+ of the steps to daemonize a process.
+
+ \value [since 6.7] DisconnectControllingTerminal Requests that the process
+ disconnect from its controlling terminal, if it has one. If it has
+ none, nothing happens. Processes still connected to a controlling
+ terminal may get a Hang Up (\c SIGHUP) signal if the terminal
+ closes, or one of the other terminal-control signals (\c SIGTSTP, \c
+ SIGTTIN, \c SIGTTOU). Note that on some operating systems, a process
+ may only disconnect from the controlling terminal if it is the
+ session leader, meaning the \c CreateNewSession flag may be
+ required. Like it, this is one of the steps to daemonize a process.
\value IgnoreSigPipe Always sets the \c SIGPIPE signal to ignored
(\c SIG_IGN), even if the \c ResetSignalHandlers flag was set. By
@@ -848,6 +877,12 @@ void QProcessPrivate::Channel::clear()
terminate immediately; with this flag, the write operation fails
without a signal and the child may continue executing.
+ \value [since 6.7] ResetIds Drops any retained, effective user or group
+ ID the current process may still have (see \c{setuid(2)} and
+ \c{setgid(2)}, plus QCoreApplication::setSetuidAllowed()). This is
+ useful if the current process was setuid or setgid and does not wish
+ the child process to retain the elevated privileges.
+
\value ResetSignalHandlers Resets all Unix signal handlers back to their
default state (that is, pass \c SIG_DFL to \c{signal(2)}). This flag
is useful to ensure any ignored (\c SIG_IGN) signal does not affect
@@ -1637,8 +1672,10 @@ std::function<void(void)> QProcess::childProcessModifier() const
\snippet code/src_corelib_io_qprocess.cpp 4
- If the modifier function needs to exit the process, remember to use
- \c{_exit()}, not \c{exit()}.
+ If the modifier function experiences a failure condition, it can use
+ failChildProcessModifier() to report the situation to the QProcess caller.
+ Alternatively, it may use other methods of stopping the process, like
+ \c{_exit()}, or \c{abort()}.
Certain properties of the child process, such as closing all extraneous
file descriptors or disconnecting from the controlling TTY, can be more
@@ -1665,7 +1702,7 @@ std::function<void(void)> QProcess::childProcessModifier() const
only make use of low-level system calls, such as \c{read()},
\c{write()}, \c{setsid()}, \c{nice()}, and similar.
- \sa childProcessModifier(), setUnixProcessParameters()
+ \sa childProcessModifier(), failChildProcessModifier(), setUnixProcessParameters()
*/
void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier)
{
@@ -1676,6 +1713,46 @@ void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier
}
/*!
+ \fn void QProcess::failChildProcessModifier(const char *description, int error) noexcept
+ \since 6.7
+
+ This functions can be used inside the modifier set with
+ setChildProcessModifier() to indicate an error condition was encountered.
+ When the modifier calls these functions, QProcess will emit errorOccurred()
+ with code QProcess::FailedToStart in the parent process. The \a description
+ can be used to include some information in errorString() to help diagnose
+ the problem, usually the name of the call that failed, similar to the C
+ Library function \c{perror()}. Additionally, the \a error parameter can be
+ an \c{<errno.h>} error code whose text form will also be included.
+
+ For example, a child modifier could prepare an extra file descriptor for
+ the child process this way:
+
+ \code
+ process.setChildProcessModifier([fd, &process]() {
+ if (dup2(fd, TargetFileDescriptor) < 0)
+ process.failChildProcessModifier(errno, "aux comm channel");
+ });
+ process.start();
+ \endcode
+
+ Where \c{fd} is a file descriptor currently open in the parent process. If
+ the \c{dup2()} system call resulted in an \c EBADF condition, the process
+ errorString() could be "Child process modifier reported error: aux comm
+ channel: Bad file descriptor".
+
+ This function does not return to the caller. Using it anywhere except in
+ the child modifier and with the correct QProcess object is undefined
+ behavior.
+
+ \note The implementation imposes a length limit to the \a description
+ parameter to about 500 characters. This does not include the text from the
+ \a error code.
+
+ \sa setChildProcessModifier(), setUnixProcessParameters()
+*/
+
+/*!
\since 6.6
Returns the \l UnixProcessParameters object describing extra flags and
settings that will be applied to the child process on Unix systems. The
@@ -1912,7 +1989,8 @@ QProcessEnvironment QProcess::processEnvironment() const
Returns \c true if the process was started successfully; otherwise
returns \c false (if the operation timed out or if an error
- occurred).
+ occurred). If the process had already started successfully before this
+ function, it returns immediately.
This function can operate without an event loop. It is
useful when writing non-GUI applications and when performing
@@ -1923,9 +2001,6 @@ QProcessEnvironment QProcess::processEnvironment() const
If msecs is -1, this function will not time out.
- \note On some UNIX operating systems, this function may return true but
- the process may later report a QProcess::FailedToStart error.
-
\sa started(), waitForReadyRead(), waitForBytesWritten(), waitForFinished()
*/
bool QProcess::waitForStarted(int msecs)
@@ -2091,18 +2166,22 @@ QByteArray QProcess::readAllStandardError()
/*!
Starts the given \a program in a new process, passing the command line
arguments in \a arguments. See setProgram() for information about how
- QProcess searches for the executable to be run.
+ QProcess searches for the executable to be run. The OpenMode is set to \a
+ mode. No further splitting of the arguments is performed.
The QProcess object will immediately enter the Starting state. If the
process starts successfully, QProcess will emit started(); otherwise,
- errorOccurred() will be emitted.
-
- \note Processes are started asynchronously, which means the started()
- and errorOccurred() signals may be delayed. Call waitForStarted() to make
- sure the process has started (or has failed to start) and those signals
- have been emitted.
-
- \note No further splitting of the arguments is performed.
+ errorOccurred() will be emitted. Do note that on platforms that are able to
+ start child processes synchronously (notably Windows), those signals will
+ be emitted before this function returns and this QProcess object will
+ transition to either QProcess::Running or QProcess::NotRunning state,
+ respectively. On others paltforms, the started() and errorOccurred()
+ signals will be delayed.
+
+ Call waitForStarted() to make sure the process has started (or has failed
+ to start) and those signals have been emitted. It is safe to call that
+ function even if the process starting state is already known, though the
+ signal will not be emitted again.
\b{Windows:} The arguments are quoted and joined into a command line
that is compatible with the \c CommandLineToArgvW() Windows function.
@@ -2111,12 +2190,16 @@ QByteArray QProcess::readAllStandardError()
not follow the \c CommandLineToArgvW() rules is cmd.exe and, by
consequence, all batch scripts.
- The OpenMode is set to \a mode.
-
If the QProcess object is already running a process, a warning may be
printed at the console, and the existing process will continue running
unaffected.
+ \note Success at starting the child process only implies the operating
+ system has successfully created the process and assigned the resources
+ every process has, such as its process ID. The child process may crash or
+ otherwise fail very early and thus not produce its expected output. On most
+ operating systems, this may include dynamic linking errors.
+
\sa processId(), started(), waitForStarted(), setNativeArguments()
*/
void QProcess::start(const QString &program, const QStringList &arguments, OpenMode mode)
@@ -2198,6 +2281,10 @@ void QProcess::start(OpenMode mode)
void QProcess::startCommand(const QString &command, OpenMode mode)
{
QStringList args = splitCommand(command);
+ if (args.isEmpty()) {
+ qWarning("QProcess::startCommand: empty or whitespace-only command was provided");
+ return;
+ }
const QString program = args.takeFirst();
start(program, args, mode);
}
diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h
index b8923b032d..34724a6794 100644
--- a/src/corelib/io/qprocess.h
+++ b/src/corelib/io/qprocess.h
@@ -5,6 +5,7 @@
#ifndef QPROCESS_H
#define QPROCESS_H
+#include <QtCore/qcompare.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qshareddata.h>
@@ -41,9 +42,11 @@ public:
void swap(QProcessEnvironment &other) noexcept { d.swap(other.d); }
+#if QT_CORE_REMOVED_SINCE(6, 8)
bool operator==(const QProcessEnvironment &other) const;
inline bool operator!=(const QProcessEnvironment &other) const
- { return !(*this == other); }
+ { return !operator==(other); }
+#endif
bool isEmpty() const;
[[nodiscard]] bool inheritsFromParent() const;
@@ -63,6 +66,9 @@ public:
static QProcessEnvironment systemEnvironment();
private:
+ friend Q_CORE_EXPORT bool comparesEqual(const QProcessEnvironment &lhs,
+ const QProcessEnvironment &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QProcessEnvironment)
friend class QProcessPrivate;
friend class QProcessEnvironmentPrivate;
QSharedDataPointer<QProcessEnvironmentPrivate> d;
@@ -174,20 +180,25 @@ public:
#if defined(Q_OS_UNIX) || defined(Q_QDOC)
std::function<void(void)> childProcessModifier() const;
void setChildProcessModifier(const std::function<void(void)> &modifier);
+ Q_NORETURN void failChildProcessModifier(const char *description, int error = 0) noexcept;
- enum UnixProcessFlag : quint32 {
+ enum class UnixProcessFlag : quint32 {
ResetSignalHandlers = 0x0001, // like POSIX_SPAWN_SETSIGDEF
IgnoreSigPipe = 0x0002,
// some room if we want to add IgnoreSigHup or so
- CloseNonStandardFileDescriptors = 0x0010,
+ CloseFileDescriptors = 0x0010,
UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK
+ CreateNewSession = 0x0040, // like POSIX_SPAWN_SETSID
+ DisconnectControllingTerminal = 0x0080,
+ ResetIds = 0x0100, // like POSIX_SPAWN_RESETIDS
};
Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag)
struct UnixProcessParameters
{
UnixProcessFlags flags = {};
+ int lowestFileDescriptorToClose = 0;
- quint32 _reserved[7] {};
+ quint32 _reserved[6] {};
};
UnixProcessParameters unixProcessParameters() const noexcept;
void setUnixProcessParameters(const UnixProcessParameters &params);
diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h
index 3b862bc5f3..9510101e74 100644
--- a/src/corelib/io/qprocess_p.h
+++ b/src/corelib/io/qprocess_p.h
@@ -304,7 +304,6 @@ public:
void startProcess();
#if defined(Q_OS_UNIX)
void commitChannels() const;
- void execChild(int workingDirectory, char **argv, char **envp) const noexcept;
#endif
bool processStarted(QString *errorMessage = nullptr);
void processFinished();
@@ -335,6 +334,9 @@ public:
void cleanup();
void setError(QProcess::ProcessError error, const QString &description = QString());
void setErrorAndEmit(QProcess::ProcessError error, const QString &description = QString());
+
+ const QProcessEnvironmentPrivate *environmentPrivate() const
+ { return environment.d.constData(); }
};
#endif // QT_CONFIG(process)
diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp
index 90ee3cb0b4..5c696433fd 100644
--- a/src/corelib/io/qprocess_unix.cpp
+++ b/src/corelib/io/qprocess_unix.cpp
@@ -37,8 +37,12 @@
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
+#include <termios.h>
#include <unistd.h>
+#if __has_include(<paths.h>)
+# include <paths.h>
+#endif
#if __has_include(<linux/close_range.h>)
// FreeBSD's is in <unistd.h>
# include <linux/close_range.h>
@@ -51,39 +55,24 @@
#ifndef O_PATH
# define O_PATH 0
#endif
+#ifndef _PATH_DEV
+# define _PATH_DEV "/dev/"
+#endif
+#ifndef _PATH_TTY
+# define _PATH_TTY _PATH_DEV "tty"
+#endif
+
+#ifdef Q_OS_FREEBSD
+__attribute__((weak))
+#endif
+extern char **environ;
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-namespace {
-struct PThreadCancelGuard
-{
-#if defined(PTHREAD_CANCEL_DISABLE)
- int oldstate;
- PThreadCancelGuard() noexcept(false)
- {
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
- }
- ~PThreadCancelGuard() noexcept(false)
- {
- reenable();
- }
- void reenable() noexcept(false)
- {
- // this doesn't touch errno
- pthread_setcancelstate(oldstate, nullptr);
- }
-#endif
-};
-}
-
#if !defined(Q_OS_DARWIN)
-QT_BEGIN_INCLUDE_NAMESPACE
-extern char **environ;
-QT_END_INCLUDE_NAMESPACE
-
QProcessEnvironment QProcessEnvironment::systemEnvironment()
{
QProcessEnvironment env;
@@ -105,6 +94,60 @@ QProcessEnvironment QProcessEnvironment::systemEnvironment()
#if QT_CONFIG(process)
+namespace QtVforkSafe {
+// Certain libc functions we need to call in the child process scenario aren't
+// safe under vfork() because they do more than just place the system call to
+// the kernel and set errno on return. For those, we'll create a function
+// pointer like:
+// static constexpr auto foobar = __libc_foobar;
+// while for all other OSes, it'll be
+// using ::foobar;
+// allowing the code for the child side of the vfork to simply use
+// QtVforkSafe::foobar(args);
+//
+// Currently known issues are:
+//
+// - FreeBSD's libthr sigaction() wrapper locks a rwlock
+// https://github.com/freebsd/freebsd-src/blob/8dad5ece49479ba6cdcd5bb4c2799bbd61add3e6/lib/libthr/thread/thr_sig.c#L575-L641
+// - MUSL's sigaction() locks a mutex if the signal is SIGABR
+// https://github.com/bminor/musl/blob/718f363bc2067b6487900eddc9180c84e7739f80/src/signal/sigaction.c#L63-L85
+//
+// All other functions called in the child side are vfork-safe, provided that
+// PThread cancellation is disabled and Unix signals are blocked.
+#if defined(__MUSL__)
+# define LIBC_PREFIX __libc_
+#elif defined(Q_OS_FREEBSD)
+// will cause QtCore to link to ELF version "FBSDprivate_1.0"
+# define LIBC_PREFIX _
+#endif
+
+#ifdef LIBC_PREFIX
+# define CONCAT(x, y) CONCAT2(x, y)
+# define CONCAT2(x, y) x ## y
+# define DECLARE_FUNCTIONS(NAME) \
+ extern decltype(::NAME) CONCAT(LIBC_PREFIX, NAME); \
+ static constexpr auto NAME = std::addressof(CONCAT(LIBC_PREFIX, NAME));
+#else // LIBC_PREFIX
+# define DECLARE_FUNCTIONS(NAME) using ::NAME;
+#endif // LIBC_PREFIX
+
+extern "C" {
+DECLARE_FUNCTIONS(sigaction)
+}
+
+#undef LIBC_PREFIX
+#undef DECLARE_FUNCTIONS
+
+// similar to qt_ignore_sigpipe() in qcore_unix_p.h, but vfork-safe
+static void change_sigpipe(decltype(SIG_DFL) new_handler)
+{
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = new_handler;
+ sigaction(SIGPIPE, &sa, nullptr);
+}
+} // namespace QtVforkSafe
+
static int opendirfd(QByteArray encodedName)
{
// We append "/." to the name to ensure that the directory is actually
@@ -139,21 +182,12 @@ struct AutoPipe
struct ChildError
{
int code;
- char function[12];
-};
-
-// Used for argv and envp arguments to execve()
-struct CharPointerList
-{
- std::unique_ptr<char *[]> pointers;
-
- CharPointerList(const QString &argv0, const QStringList &args);
- explicit CharPointerList(const QProcessEnvironmentPrivate *env);
-
-private:
- QByteArray data;
- void updatePointers(qsizetype count);
+ char function[_POSIX_PIPE_BUF - sizeof(code)];
};
+static_assert(std::is_trivial_v<ChildError>);
+#ifdef PIPE_BUF
+static_assert(PIPE_BUF >= sizeof(ChildError)); // PIPE_BUF may be bigger
+#endif
struct QProcessPoller
{
@@ -188,10 +222,139 @@ QProcessPoller::QProcessPoller(const QProcessPrivate &proc)
int QProcessPoller::poll(const QDeadlineTimer &deadline)
{
- return qt_poll_msecs(pfds, n_pfds, deadline.remainingTime());
+ return qt_safe_poll(pfds, n_pfds, deadline);
}
-CharPointerList::CharPointerList(const QString &program, const QStringList &args)
+struct QChildProcess
+{
+ // Used for argv and envp arguments to execve()
+ struct CharPointerList
+ {
+ std::unique_ptr<char *[]> pointers;
+
+ CharPointerList(const QString &argv0, const QStringList &args);
+ explicit CharPointerList(const QProcessEnvironmentPrivate *env);
+ /*implicit*/ operator char **() const { return pointers.get(); }
+
+ private:
+ QByteArray data;
+ void updatePointers(qsizetype count);
+ };
+
+ const QProcessPrivate *d;
+ CharPointerList argv;
+ CharPointerList envp;
+ sigset_t oldsigset;
+ int workingDirectory = -2;
+ bool isUsingVfork = usingVfork();
+
+ bool ok() const
+ {
+ return workingDirectory != -1;
+ }
+
+ QChildProcess(QProcessPrivate *d)
+ : d(d), argv(resolveExecutable(d->program), d->arguments),
+ envp(d->environmentPrivate())
+ {
+ // Block Unix signals, to ensure the user's handlers aren't run in the
+ // child side and do something weird, especially if the handler and the
+ // user of QProcess are completely different codebases.
+ maybeBlockSignals();
+
+ // Disable PThread cancellation until the child has successfully been
+ // executed. We make a number of POSIX calls in the child that are thread
+ // cancellation points and could cause an unexpected stack unwind. That
+ // would be bad enough with regular fork(), but it's likely fatal with
+ // vfork().
+ disableThreadCancellations();
+
+ if (!d->workingDirectory.isEmpty()) {
+ workingDirectory = opendirfd(QFile::encodeName(d->workingDirectory));
+ if (workingDirectory < 0) {
+ d->setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string());
+ d->cleanup();
+ }
+ }
+
+ }
+ ~QChildProcess() noexcept(false)
+ {
+ if (workingDirectory >= 0)
+ close(workingDirectory);
+
+ restoreThreadCancellations();
+ restoreSignalMask();
+ }
+
+ void maybeBlockSignals() noexcept
+ {
+ // We only block Unix signals if we're using vfork(), to avoid a
+ // changing behavior to the user's modifier and because in some OSes
+ // this action would block crashing signals too.
+ if (isUsingVfork) {
+ sigset_t emptyset;
+ sigfillset(&emptyset);
+ pthread_sigmask(SIG_SETMASK, &emptyset, &oldsigset);
+ }
+ }
+
+ void restoreSignalMask() const noexcept
+ {
+ if (isUsingVfork)
+ pthread_sigmask(SIG_SETMASK, &oldsigset, nullptr);
+ }
+
+ bool usingVfork() const noexcept;
+
+ template <typename Lambda> int doFork(Lambda &&childLambda)
+ {
+ pid_t pid;
+ if (isUsingVfork) {
+ QT_IGNORE_DEPRECATIONS(pid = vfork();)
+ } else {
+ pid = fork();
+ }
+ if (pid == 0)
+ _exit(childLambda());
+ return pid;
+ }
+
+ int startChild(pid_t *pid)
+ {
+ int ffdflags = FFD_CLOEXEC | (isUsingVfork ? 0 : FFD_USE_FORK);
+ return ::vforkfd(ffdflags, pid, &QChildProcess::startProcess, this);
+ }
+
+private:
+ Q_NORETURN void startProcess() const noexcept;
+ static int startProcess(void *self) noexcept
+ {
+ static_cast<QChildProcess *>(self)->startProcess();
+ Q_UNREACHABLE_RETURN(-1);
+ }
+
+#if defined(PTHREAD_CANCEL_DISABLE)
+ int oldstate;
+ void disableThreadCancellations() noexcept
+ {
+ // the following is *not* noexcept, but it won't throw while disabling
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
+ }
+ void restoreThreadCancellations() noexcept(false)
+ {
+ // this doesn't touch errno
+ pthread_setcancelstate(oldstate, nullptr);
+ }
+#else
+ void disableThreadCancellations() noexcept {}
+ void restoreThreadCancellations() {}
+#endif
+
+ static QString resolveExecutable(const QString &program);
+};
+
+QChildProcess::CharPointerList::CharPointerList(const QString &program, const QStringList &args)
{
qsizetype count = 1 + args.size();
pointers.reset(new char *[count + 1]);
@@ -214,7 +377,7 @@ CharPointerList::CharPointerList(const QString &program, const QStringList &args
updatePointers(count);
}
-CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
+QChildProcess::CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
{
if (!environment)
return;
@@ -240,7 +403,7 @@ CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
updatePointers(count);
}
-void CharPointerList::updatePointers(qsizetype count)
+void QChildProcess::CharPointerList::updatePointers(qsizetype count)
{
char *const base = const_cast<char *>(data.constBegin());
for (qsizetype i = 0; i < count; ++i)
@@ -261,7 +424,8 @@ static int qt_create_pipe(int *pipe)
qt_safe_close(pipe[1]);
int pipe_ret = qt_safe_pipe(pipe);
if (pipe_ret != 0) {
- qErrnoWarning("QProcessPrivate::createPipe: Cannot create pipe %p", pipe);
+ QScopedValueRollback rollback(errno);
+ qErrnoWarning("QProcess: Cannot create pipe");
}
return pipe_ret;
}
@@ -310,8 +474,10 @@ bool QProcessPrivate::openChannel(Channel &channel)
if (channel.type == Channel::Normal) {
// we're piping this channel to our own process
- if (qt_create_pipe(channel.pipe) != 0)
+ if (qt_create_pipe(channel.pipe) != 0) {
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
return false;
+ }
// create the socket notifiers
if (threadData.loadRelaxed()->hasEventDispatcher()) {
@@ -359,7 +525,6 @@ bool QProcessPrivate::openChannel(Channel &channel)
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Could not open input redirection for reading"));
}
- cleanup();
return false;
} else {
Q_ASSERT_X(channel.process, "QProcess::start", "Internal error");
@@ -391,8 +556,10 @@ bool QProcessPrivate::openChannel(Channel &channel)
Q_ASSERT(sink->pipe[0] == INVALID_Q_PIPE && sink->pipe[1] == INVALID_Q_PIPE);
Q_PIPE pipe[2] = { -1, -1 };
- if (qt_create_pipe(pipe) != 0)
+ if (qt_create_pipe(pipe) != 0) {
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
return false;
+ }
sink->pipe[0] = pipe[0];
source->pipe[1] = pipe[1];
@@ -419,7 +586,7 @@ void QProcessPrivate::commitChannels() const
}
}
-static QString resolveExecutable(const QString &program)
+inline QString QChildProcess::resolveExecutable(const QString &program)
{
#ifdef Q_OS_DARWIN
// allow invoking of .app bundles on the Mac.
@@ -455,34 +622,63 @@ static QString resolveExecutable(const QString &program)
return program;
}
-static int useForkFlags(const QProcessPrivate::UnixExtras *unixExtras)
+extern "C" {
+__attribute__((weak)) pid_t __interceptor_vfork();
+}
+
+inline bool globalUsingVfork() noexcept
{
+#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
+ // ASan writes to global memory, so we mustn't use vfork().
+ return false;
+#endif
+#if defined(__SANITIZE_THREAD__) || __has_feature(thread_sanitizer)
+ // Ditto, apparently
+ return false;
+#endif
#if defined(Q_OS_LINUX) && !QT_CONFIG(forkfd_pidfd)
// some broken environments are known to have problems with the new Linux
// API, so we have a way for users to opt-out during configure time (see
// QTBUG-86285)
- return FFD_USE_FORK;
+ return false;
#endif
#if defined(Q_OS_DARWIN)
// Using vfork() for startDetached() is causing problems. We don't know
// why: without the tools to investigate why it happens, we didn't bother.
- return FFD_USE_FORK;
+ return false;
#endif
- if (!unixExtras || !unixExtras->childProcessModifier)
- return 0; // no modifier was supplied
+ // Dynamically detect whether libasan or libtsan are loaded into the
+ // process' memory. We need this because the user's code may be compiled
+ // with ASan or TSan, but not Qt.
+ return __interceptor_vfork == nullptr;
+}
+
+inline bool QChildProcess::usingVfork() const noexcept
+{
+ if (!globalUsingVfork())
+ return false;
+
+ if (!d->unixExtras || !d->unixExtras->childProcessModifier)
+ return true; // no modifier was supplied
// if a modifier was supplied, use fork() unless the user opts in to
// vfork()
- auto flags = unixExtras->processParameters.flags;
- if (flags.testFlag(QProcess::UnixProcessFlag::UseVFork))
- return 0;
- return FFD_USE_FORK;
+ auto flags = d->unixExtras->processParameters.flags;
+ return flags.testFlag(QProcess::UnixProcessFlag::UseVFork);
+}
+
+#ifdef QT_BUILD_INTERNAL
+Q_AUTOTEST_EXPORT bool _qprocessUsingVfork() noexcept
+{
+ return globalUsingVfork();
}
+#endif
void QProcessPrivate::startProcess()
{
Q_Q(QProcess);
+ q->setProcessState(QProcess::Starting);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::startProcess()");
@@ -491,6 +687,8 @@ void QProcessPrivate::startProcess()
// Initialize pipes
if (!openChannels()) {
// openChannel sets the error string
+ Q_ASSERT(!errorString.isEmpty());
+ cleanup();
return;
}
if (qt_create_pipe(childStartedPipe) != 0) {
@@ -509,44 +707,17 @@ void QProcessPrivate::startProcess()
q, SLOT(_q_startupNotification()));
}
- int workingDirFd = -1;
- if (!workingDirectory.isEmpty()) {
- workingDirFd = opendirfd(QFile::encodeName(workingDirectory));
- if (workingDirFd == -1) {
- setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string());
- cleanup();
- return;
- }
- }
-
- // Start the process (platform dependent)
- q->setProcessState(QProcess::Starting);
-
// Prepare the arguments and the environment
- const CharPointerList argv(resolveExecutable(program), arguments);
- const CharPointerList envp(environment.d.constData());
-
- // Disable PThread cancellation from this point on: we mustn't have it
- // enabled when the child starts running nor while our state could get
- // corrupted if we abruptly exited this function.
- [[maybe_unused]] PThreadCancelGuard cancelGuard;
+ QChildProcess childProcess(this);
+ if (!childProcess.ok()) {
+ Q_ASSERT(processError != QProcess::UnknownError);
+ return;
+ }
// Start the child.
- auto execChild1 = [this, workingDirFd, &argv, &envp]() {
- execChild(workingDirFd, argv.pointers.get(), envp.pointers.get());
- };
- auto execChild2 = [](void *lambda) {
- static_cast<decltype(execChild1) *>(lambda)->operator()();
- return -1;
- };
-
- int ffdflags = FFD_CLOEXEC | useForkFlags(unixExtras.get());
- forkfd = ::vforkfd(ffdflags, &pid, execChild2, &execChild1);
+ forkfd = childProcess.startChild(&pid);
int lastForkErrno = errno;
- if (workingDirFd != -1)
- close(workingDirFd);
-
if (forkfd == -1) {
// Cleanup, report error and return
#if defined (QPROCESS_DEBUG)
@@ -592,34 +763,72 @@ void QProcessPrivate::startProcess()
// we need an errno number to use to indicate the child process modifier threw,
// something the regular operations shouldn't set.
-static constexpr int FakeErrnoForThrow =
-#ifdef ECANCELED
- ECANCELED
-#else
- ESHUTDOWN
-#endif
- ;
+static constexpr int FakeErrnoForThrow = std::numeric_limits<int>::max();
+
+static QString startFailureErrorMessage(ChildError &err, [[maybe_unused]] ssize_t bytesRead)
+{
+ // ChildError is less than the POSIX pipe buffer atomic size, so the read
+ // must not have been truncated
+ Q_ASSERT(bytesRead == sizeof(err));
+
+ qsizetype len = qstrnlen(err.function, sizeof(err.function));
+ QString complement = QString::fromUtf8(err.function, len);
+ if (err.code == FakeErrnoForThrow)
+ return QProcess::tr("Child process modifier threw an exception: %1")
+ .arg(std::move(complement));
+ if (err.code == 0)
+ return QProcess::tr("Child process modifier reported error: %1")
+ .arg(std::move(complement));
+ if (err.code < 0)
+ return QProcess::tr("Child process modifier reported error: %1: %2")
+ .arg(std::move(complement), qt_error_string(-err.code));
+ return QProcess::tr("Child process set up failed: %1: %2")
+ .arg(std::move(complement), qt_error_string(err.code));
+}
+
+Q_NORETURN void
+failChildProcess(const QProcessPrivate *d, const char *description, int code) noexcept
+{
+ ChildError error = {};
+ error.code = code;
+ qstrncpy(error.function, description, sizeof(error.function));
+ qt_safe_write(d->childStartedPipe[1], &error, sizeof(error));
+ _exit(-1);
+}
+
+void QProcess::failChildProcessModifier(const char *description, int error) noexcept
+{
+ // We signal user errors with negative errnos
+ failChildProcess(d_func(), description, -error);
+}
// See IMPORTANT notice below
-static void applyProcessParameters(const QProcess::UnixProcessParameters &params)
+static const char *applyProcessParameters(const QProcess::UnixProcessParameters &params)
{
// Apply Unix signal handler parameters.
// We don't expect signal() to fail, so we ignore its return value
bool ignore_sigpipe = params.flags.testFlag(QProcess::UnixProcessFlag::IgnoreSigPipe);
if (ignore_sigpipe)
- signal(SIGPIPE, SIG_IGN); // don't use qt_ignore_sigpipe!
+ QtVforkSafe::change_sigpipe(SIG_IGN);
if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetSignalHandlers)) {
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_DFL;
for (int sig = 1; sig < NSIG; ++sig) {
if (!ignore_sigpipe || sig != SIGPIPE)
- signal(sig, SIG_DFL);
+ QtVforkSafe::sigaction(sig, &sa, nullptr);
}
+
+ // and unmask all signals
+ sigset_t set;
+ sigemptyset(&set);
+ sigprocmask(SIG_SETMASK, &set, nullptr);
}
// Close all file descriptors above stderr.
// This isn't expected to fail, so we ignore close()'s return value.
- if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseNonStandardFileDescriptors)) {
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseFileDescriptors)) {
int r = -1;
- int fd = STDERR_FILENO + 1;
+ int fd = qMax(STDERR_FILENO + 1, params.lowestFileDescriptorToClose);
#if QT_CONFIG(close_range)
// On FreeBSD, this probably won't fail.
// On Linux, this will fail with ENOSYS before kernel 5.9.
@@ -637,73 +846,102 @@ static void applyProcessParameters(const QProcess::UnixProcessParameters &params
close(fd);
}
}
+
+ // Apply session and process group settings. This may fail.
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::CreateNewSession)) {
+ if (setsid() < 0)
+ return "setsid";
+ }
+
+ // Disconnect from the controlling TTY. This probably won't fail. Must be
+ // done after the session settings from above.
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::DisconnectControllingTerminal)) {
+ if (int fd = open(_PATH_TTY, O_RDONLY | O_NOCTTY); fd >= 0) {
+ // we still have a controlling TTY; give it up
+ int r = ioctl(fd, TIOCNOTTY);
+ int savedErrno = errno;
+ close(fd);
+ if (r != 0) {
+ errno = savedErrno;
+ return "ioctl";
+ }
+ }
+ }
+
+ // Apply UID and GID parameters last. This isn't expected to fail either:
+ // either we're trying to impersonate what we already are, or we're EUID or
+ // EGID root, in which case we are allowed to do this.
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetIds)) {
+ int r = setgid(getgid());
+ r = setuid(getuid());
+ (void) r;
+ }
+
+ return nullptr;
}
// the noexcept here adds an extra layer of protection
-static const char *callChildProcessModifier(const QProcessPrivate::UnixExtras *unixExtras) noexcept
+static void callChildProcessModifier(const QProcessPrivate *d) noexcept
{
QT_TRY {
- if (unixExtras->childProcessModifier)
- unixExtras->childProcessModifier();
+ if (d->unixExtras->childProcessModifier)
+ d->unixExtras->childProcessModifier();
+ } QT_CATCH (std::exception &e) {
+ failChildProcess(d, e.what(), FakeErrnoForThrow);
} QT_CATCH (...) {
- errno = FakeErrnoForThrow;
- return "throw";
+ failChildProcess(d, "throw", FakeErrnoForThrow);
}
- return nullptr;
}
-// this function doesn't return if the execution succeeds
-static const char *doExecChild(char **argv, char **envp, int workingDirFd,
- const QProcessPrivate::UnixExtras *unixExtras) noexcept
+// IMPORTANT:
+//
+// This function is called in a vfork() context on some OSes (notably, Linux
+// with forkfd), so it MUST NOT modify any non-local variable because it's
+// still sharing memory with the parent process.
+void QChildProcess::startProcess() const noexcept
{
+ // Render channels configuration.
+ d->commitChannels();
+
+ // make sure this fd is closed if execv() succeeds
+ qt_safe_close(d->childStartedPipe[0]);
+
// enter the working directory
- if (workingDirFd != -1 && fchdir(workingDirFd) == -1)
- return "fchdir";
+ if (workingDirectory >= 0 && fchdir(workingDirectory) == -1)
+ failChildProcess(d, "fchdir", errno);
- if (unixExtras) {
+ bool sigpipeHandled = false;
+ bool sigmaskHandled = false;
+ if (d->unixExtras) {
// FIRST we call the user modifier function, before we dropping
// privileges or closing non-standard file descriptors
- if (const char *what = callChildProcessModifier(unixExtras))
- return what;
+ callChildProcessModifier(d);
// then we apply our other user-provided parameters
- applyProcessParameters(unixExtras->processParameters);
+ if (const char *what = applyProcessParameters(d->unixExtras->processParameters))
+ failChildProcess(d, what, errno);
+
+ auto flags = d->unixExtras->processParameters.flags;
+ using P = QProcess::UnixProcessFlag;
+ sigpipeHandled = flags.testAnyFlags(P::ResetSignalHandlers | P::IgnoreSigPipe);
+ sigmaskHandled = flags.testFlag(P::ResetSignalHandlers);
+ }
+ if (!sigpipeHandled) {
+ // reset the signal that we ignored
+ QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored
+ }
+ if (!sigmaskHandled) {
+ // restore the signal mask from the parent, if applyProcessParameters()
+ // hasn't completely reset it
+ restoreSignalMask();
}
// execute the process
- if (!envp)
+ if (!envp.pointers)
qt_safe_execv(argv[0], argv);
else
qt_safe_execve(argv[0], argv, envp);
- return "execve";
-}
-
-
-// IMPORTANT:
-//
-// This function is called in a vfork() context on some OSes (notably, Linux
-// with forkfd), so it MUST NOT modify any non-local variable because it's
-// still sharing memory with the parent process.
-void QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const noexcept
-{
- ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
-
- ChildError error = { 0, {} }; // force zeroing of function[8]
-
- // Render channels configuration.
- commitChannels();
-
- // make sure this fd is closed if execv() succeeds
- qt_safe_close(childStartedPipe[0]);
-
- const char *what = doExecChild(argv, envp, workingDir, unixExtras.get());
- strcpy(error.function, what);
-
- // notify failure
- // don't use strerror or any other routines that may allocate memory, since
- // some buggy libc versions can deadlock on locked mutexes.
- error.code = errno;
- qt_safe_write(childStartedPipe[1], &error, sizeof(error));
+ failChildProcess(d, "execve", errno);
}
bool QProcessPrivate::processStarted(QString *errorMessage)
@@ -740,12 +978,8 @@ bool QProcessPrivate::processStarted(QString *errorMessage)
}
// did we read an error message?
- if (errorMessage) {
- if (buf.code == FakeErrnoForThrow)
- *errorMessage = QProcess::tr("childProcessModifier() function threw an exception");
- else
- *errorMessage = QLatin1StringView(buf.function) + ": "_L1 + qt_error_string(buf.code);
- }
+ if (errorMessage)
+ *errorMessage = startFailureErrorMessage(buf, ret);
return false;
}
@@ -877,15 +1111,15 @@ void QProcessPrivate::killProcess()
bool QProcessPrivate::waitForStarted(const QDeadlineTimer &deadline)
{
- const qint64 msecs = deadline.remainingTime();
#if defined (QPROCESS_DEBUG)
+ const qint64 msecs = deadline.remainingTime();
qDebug("QProcessPrivate::waitForStarted(%lld) waiting for child to start (fd = %d)",
msecs, childStartedPipe[0]);
#endif
pollfd pfd = qt_make_pollfd(childStartedPipe[0], POLLIN);
- if (qt_poll_msecs(&pfd, 1, msecs) == 0) {
+ if (qt_safe_poll(&pfd, 1, deadline) == 0) {
setError(QProcess::Timedout);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForStarted(%lld) == false (timed out)", msecs);
@@ -1034,9 +1268,10 @@ void QProcessPrivate::waitForDeadChild()
Q_ASSERT(forkfd != -1);
// read the process information from our fd
- forkfd_info info;
+ forkfd_info info = {}; // Silence -Wmaybe-uninitialized; Thiago says forkfd_wait cannot fail here
+ // (QTBUG-119081)
int ret;
- EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr));
+ QT_EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr));
exitCode = info.status;
exitStatus = info.code == CLD_EXITED ? QProcess::NormalExit : QProcess::CrashExit;
@@ -1044,7 +1279,7 @@ void QProcessPrivate::waitForDeadChild()
delete stateNotifier;
stateNotifier = nullptr;
- EINTR_LOOP(ret, forkfd_close(forkfd));
+ QT_EINTR_LOOP(ret, forkfd_close(forkfd));
forkfd = -1; // Child is dead, don't try to kill it anymore
#if defined QPROCESS_DEBUG
@@ -1055,14 +1290,6 @@ void QProcessPrivate::waitForDeadChild()
bool QProcessPrivate::startDetached(qint64 *pid)
{
-
-#ifdef PIPE_BUF
- static_assert(PIPE_BUF >= sizeof(ChildError));
-#else
- static_assert(_POSIX_PIPE_BUF >= sizeof(ChildError));
-#endif
- ChildError childStatus = { 0, {} };
-
AutoPipe startedPipe, pidPipe;
if (!startedPipe || !pidPipe) {
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
@@ -1075,61 +1302,32 @@ bool QProcessPrivate::startDetached(qint64 *pid)
return false;
}
- int workingDirFd = -1;
- if (!workingDirectory.isEmpty()) {
- workingDirFd = opendirfd(QFile::encodeName(workingDirectory));
- if (workingDirFd == -1) {
- setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string(errno));
- return false;
- }
+ // see startProcess() for more information
+ QChildProcess childProcess(this);
+ if (!childProcess.ok()) {
+ Q_ASSERT(processError != QProcess::UnknownError);
+ return false;
}
- const CharPointerList argv(resolveExecutable(program), arguments);
- const CharPointerList envp(environment.d.constData());
-
- // see startProcess() for more information
- [[maybe_unused]] PThreadCancelGuard cancelGuard;
-
- auto doFork = [this]() {
- if (useForkFlags(unixExtras.get()))
- return fork;
- QT_IGNORE_DEPRECATIONS(return vfork;)
- }();
- pid_t childPid = doFork();
- if (childPid == 0) {
- ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
+ childStartedPipe[1] = startedPipe[1]; // for failChildProcess()
+ pid_t childPid = childProcess.doFork([&] {
::setsid();
qt_safe_close(startedPipe[0]);
qt_safe_close(pidPipe[0]);
- auto reportFailed = [&](const char *function) {
- childStatus.code = errno;
- strcpy(childStatus.function, function);
- qt_safe_write(startedPipe[1], &childStatus, sizeof(childStatus));
- ::_exit(1);
- };
-
- pid_t doubleForkPid = doFork();
- if (doubleForkPid == 0) {
- // Render channels configuration.
- commitChannels();
-
- reportFailed(doExecChild(argv.pointers.get(), envp.pointers.get(), workingDirFd,
- unixExtras.get()));
- } else if (doubleForkPid == -1) {
- reportFailed("fork: ");
- }
+ pid_t doubleForkPid;
+ if (childProcess.startChild(&doubleForkPid) == -1)
+ failChildProcess(this, "fork", errno);
// success
qt_safe_write(pidPipe[1], &doubleForkPid, sizeof(pid_t));
- ::_exit(1);
- }
+ return 0;
+ });
+ childStartedPipe[1] = -1;
int savedErrno = errno;
closeChannels();
- if (workingDirFd != -1)
- close(workingDirFd);
if (childPid == -1) {
setErrorAndEmit(QProcess::FailedToStart, "fork: "_L1 + qt_error_string(savedErrno));
@@ -1146,6 +1344,7 @@ bool QProcessPrivate::startDetached(qint64 *pid)
// successfully execve()'d the target process. If it returns any positive
// result, it means one of the two children wrote an error result. Negative
// values should not happen.
+ ChildError childStatus;
ssize_t startResult = qt_safe_read(startedPipe[0], &childStatus, sizeof(childStatus));
// reap the intermediate child
@@ -1161,10 +1360,8 @@ bool QProcessPrivate::startDetached(qint64 *pid)
} else if (!success) {
if (pid)
*pid = -1;
- QString msg;
- if (startResult == sizeof(childStatus))
- msg = QLatin1StringView(childStatus.function) + qt_error_string(childStatus.code);
- setErrorAndEmit(QProcess::FailedToStart, msg);
+ setErrorAndEmit(QProcess::FailedToStart,
+ startFailureErrorMessage(childStatus, startResult));
}
return success;
}
diff --git a/src/corelib/io/qprocess_win.cpp b/src/corelib/io/qprocess_win.cpp
index 650e9038cc..e64b133815 100644
--- a/src/corelib/io/qprocess_win.cpp
+++ b/src/corelib/io/qprocess_win.cpp
@@ -140,6 +140,7 @@ static bool qt_create_pipe(Q_PIPE *pipe, bool isInputPipe, BOOL defInheritFlag)
DWORD dwError = GetLastError();
if (dwError != ERROR_PIPE_BUSY || !--attempts) {
qErrnoWarning(dwError, "QProcess: CreateNamedPipe failed.");
+ SetLastError(dwError);
return false;
}
}
@@ -154,8 +155,10 @@ static bool qt_create_pipe(Q_PIPE *pipe, bool isInputPipe, BOOL defInheritFlag)
FILE_FLAG_OVERLAPPED,
NULL);
if (hClient == INVALID_HANDLE_VALUE) {
+ DWORD dwError = GetLastError();
qErrnoWarning("QProcess: CreateFile failed.");
CloseHandle(hServer);
+ SetLastError(dwError);
return false;
}
@@ -172,10 +175,12 @@ static bool qt_create_pipe(Q_PIPE *pipe, bool isInputPipe, BOOL defInheritFlag)
WaitForSingleObject(overlapped.hEvent, INFINITE);
break;
default:
+ dwError = GetLastError();
qErrnoWarning(dwError, "QProcess: ConnectNamedPipe failed.");
CloseHandle(overlapped.hEvent);
CloseHandle(hClient);
CloseHandle(hServer);
+ SetLastError(dwError);
return false;
}
}
@@ -201,8 +206,13 @@ bool QProcessPrivate::openChannel(Channel &channel)
switch (channel.type) {
case Channel::Normal: {
// we're piping this channel to our own process
- if (&channel == &stdinChannel)
- return qt_create_pipe(channel.pipe, true, FALSE);
+ if (&channel == &stdinChannel) {
+ if (!qt_create_pipe(channel.pipe, true, FALSE)) {
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
+ return false;
+ }
+ return true;
+ }
if (&channel == &stdoutChannel) {
if (!stdoutChannel.reader) {
@@ -215,8 +225,10 @@ bool QProcessPrivate::openChannel(Channel &channel)
q->connect(stderrChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardError()));
}
}
- if (!qt_create_pipe(channel.pipe, false, FALSE))
+ if (!qt_create_pipe(channel.pipe, false, FALSE)) {
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
return false;
+ }
channel.reader->setHandle(channel.pipe[0]);
channel.reader->startAsyncRead();
@@ -265,7 +277,6 @@ bool QProcessPrivate::openChannel(Channel &channel)
setErrorAndEmit(QProcess::FailedToStart,
QProcess::tr("Could not open output redirection for writing"));
}
- cleanup();
return false;
}
case Channel::PipeSource: {
@@ -282,8 +293,10 @@ bool QProcessPrivate::openChannel(Channel &channel)
Q_ASSERT(source == &stdoutChannel);
Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink);
- if (!qt_create_pipe(source->pipe, /* in = */ false, TRUE)) // source is stdout
+ if (!qt_create_pipe(source->pipe, /* in = */ false, TRUE)) { // source is stdout
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
return false;
+ }
sink->pipe[0] = source->pipe[0];
source->pipe[0] = INVALID_Q_PIPE;
@@ -302,8 +315,10 @@ bool QProcessPrivate::openChannel(Channel &channel)
Q_ASSERT(sink == &stdinChannel);
Q_ASSERT(source->process == this && source->type == Channel::PipeSource);
- if (!qt_create_pipe(sink->pipe, /* in = */ true, TRUE)) // sink is stdin
+ if (!qt_create_pipe(sink->pipe, /* in = */ true, TRUE)) { // sink is stdin
+ setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
return false;
+ }
source->pipe[1] = sink->pipe[1];
sink->pipe[1] = INVALID_Q_PIPE;
@@ -523,9 +538,9 @@ void QProcessPrivate::startProcess()
q->setProcessState(QProcess::Starting);
if (!openChannels()) {
- QString errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
+ // openChannel sets the error string
+ Q_ASSERT(!errorString.isEmpty());
cleanup();
- setErrorAndEmit(QProcess::FailedToStart, errorString);
return;
}
diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp
index a3237f25bc..581e1e75ef 100644
--- a/src/corelib/io/qresource.cpp
+++ b/src/corelib/io/qresource.cpp
@@ -33,9 +33,19 @@
# include <zstd.h>
#endif
-#if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY)
+#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
# define QT_USE_MMAP
# include <sys/mman.h>
+# ifdef Q_OS_LINUX
+// since 5.7, so define in case we're being compiled with older kernel headers
+# define MREMAP_DONTUNMAP 4
+# elif defined(Q_OS_DARWIN)
+# include <mach/mach.h>
+# include <mach/vm_map.h>
+# endif
+#endif
+#ifdef Q_OS_WIN
+# include <qt_windows.h>
#endif
//#define DEBUG_RESOURCE_MATCH
@@ -65,6 +75,7 @@ RCC_FEATURE_SYMBOL(Zstd)
#undef RCC_FEATURE_SYMBOL
+namespace {
class QStringSplitter
{
public:
@@ -130,7 +141,7 @@ public:
return QResource::NoCompression;
}
const uchar *data(int node, qint64 *size) const;
- quint64 lastModified(int node) const;
+ qint64 lastModified(int node) const;
QStringList children(int node) const;
virtual QString mappingRoot() const { return QString(); }
bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
@@ -159,15 +170,18 @@ static QString cleanPath(const QString &_path)
path.remove(0, 1);
return path;
}
+} // unnamed namespace
Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE);
typedef QList<QResourceRoot*> ResourceList;
+namespace {
struct QResourceGlobalData
{
QRecursiveMutex resourceMutex;
ResourceList resourceList;
};
+}
Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
static inline QRecursiveMutex &resourceMutex()
@@ -233,6 +247,19 @@ static inline ResourceList *resourceList()
itself will be unmapped from memory when the last QResource that points
to it is destroyed.
+ \section2 Corruption and Security
+
+ The QResource class performs some checks on the file passed to determine
+ whether it is supported by the current version of Qt. Those tests are only
+ to check the file header does not request features (such as Zstandard
+ decompression) that have not been compiled in or that the file is not of a
+ future version of Qt. They do not confirm the validity of the entire file.
+
+ QResource should not be used on files whose provenance cannot be trusted.
+ Applications should be designed to attempt to load only resource files
+ whose provenance is at least as trustworthy as that of the application
+ itself or its plugins.
+
\sa {The Qt Resource System}, QFile, QDir, QFileInfo
*/
@@ -266,14 +293,16 @@ public:
bool load(const QString &file);
void clear();
+ static bool mayRemapData(const QResource &resource);
+
QLocale locale;
QString fileName, absoluteFilePath;
QList<QResourceRoot *> related;
- mutable qint64 size;
- mutable quint64 lastModified;
- mutable const uchar *data;
+ qint64 size;
+ qint64 lastModified;
+ const uchar *data;
mutable QStringList children;
- mutable quint8 compressionAlgo;
+ quint8 compressionAlgo;
bool container;
/* 2 or 6 padding bytes */
@@ -915,14 +944,14 @@ const uchar *QResourceRoot::data(int node, qint64 *size) const
return nullptr;
}
-quint64 QResourceRoot::lastModified(int node) const
+qint64 QResourceRoot::lastModified(int node) const
{
if (node == -1 || version < 0x02)
return 0;
const int offset = findOffset(node) + 14;
- return qFromBigEndian<quint64>(tree + offset);
+ return qFromBigEndian<qint64>(tree + offset);
}
QStringList QResourceRoot::children(int node) const
@@ -1018,8 +1047,8 @@ Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tre
return false;
}
+namespace {
// run time resource creation
-
class QDynamicBufferResourceRoot : public QResourceRoot
{
QString root;
@@ -1092,6 +1121,11 @@ public:
class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
{
+public:
+ static uchar *map_sys(QFile &file, qint64 base, qsizetype size);
+ static void unmap_sys(void *base, qsizetype size);
+
+private:
QString fileName;
// for mmap'ed files, this is what needs to be unmapped.
uchar *unmapPointer;
@@ -1102,22 +1136,18 @@ public:
: QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
{ }
~QDynamicFileResourceRoot() {
-#if defined(QT_USE_MMAP)
- if (unmapPointer) {
- munmap(reinterpret_cast<char *>(unmapPointer), unmapLength);
- unmapPointer = nullptr;
- unmapLength = 0;
- } else
-#endif
- {
+ if (wasMemoryMapped())
+ unmap_sys(unmapPointer, unmapLength);
+ else
delete[] mappingBuffer();
- }
}
QString mappingFile() const { return fileName; }
ResourceRootType type() const override { return Resource_File; }
+ bool wasMemoryMapped() const { return unmapPointer; }
bool registerSelf(const QString &f);
};
+} // unnamed namespace
#ifndef MAP_FILE
# define MAP_FILE 0
@@ -1126,49 +1156,69 @@ public:
# define MAP_FAILED reinterpret_cast<void *>(-1)
#endif
-bool QDynamicFileResourceRoot::registerSelf(const QString &f)
+void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size)
{
- bool fromMM = false;
- uchar *data = nullptr;
- qsizetype data_len = 0;
+#if defined(QT_USE_MMAP)
+ munmap(base, size);
+#elif defined(Q_OS_WIN)
+ Q_UNUSED(size)
+ UnmapViewOfFile(reinterpret_cast<void *>(base));
+#endif
+}
+
+// Note: caller must ensure \a offset and \a size are acceptable to the OS.
+uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size)
+{
+ Q_ASSERT(file.isOpen());
+ void *ptr = nullptr;
+ if (size < 0)
+ size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)());
+ // We don't use QFile::map() here because we want to dispose of the QFile object
#if defined(QT_USE_MMAP)
- int fd = QT_OPEN(QFile::encodeName(f), O_RDONLY);
- if (fd >= 0) {
- QT_STATBUF st;
- if (!QT_FSTAT(fd, &st) && st.st_size <= std::numeric_limits<qsizetype>::max()) {
- int protection = PROT_READ; // read-only memory
- int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
- void *ptr = QT_MMAP(nullptr, st.st_size, // any address, whole file
- protection, flags,
- fd, 0); // from offset 0 of fd
- if (ptr != MAP_FAILED) {
- data = static_cast<uchar *>(ptr);
- data_len = st.st_size;
- fromMM = true;
- }
+ int fd = file.handle();
+ int protection = PROT_READ; // read-only memory
+ int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
+ ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset);
+ if (ptr == MAP_FAILED)
+ ptr = nullptr;
+#elif defined(Q_OS_WIN)
+ int fd = file.handle();
+ HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ if (fileHandle != INVALID_HANDLE_VALUE) {
+ HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0);
+ if (mapHandle) {
+ ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size);
+ CloseHandle(mapHandle);
}
- QT_CLOSE(fd);
}
#endif // QT_USE_MMAP
- if (!data) {
- QFile file(f);
+ return static_cast<uchar *>(ptr);
+}
+
+bool QDynamicFileResourceRoot::registerSelf(const QString &f)
+{
+ QFile file(f);
+ if (!file.open(QIODevice::ReadOnly))
+ return false;
+
+ qint64 data_len = file.size();
+ if (data_len > std::numeric_limits<qsizetype>::max())
+ return false;
+
+ uchar *data = map_sys(file, 0, data_len);
+ bool fromMM = !!data;
+
+ if (!fromMM) {
bool ok = false;
- if (file.open(QIODevice::ReadOnly)) {
- qint64 fsize = file.size();
- if (fsize <= std::numeric_limits<qsizetype>::max()) {
- data_len = file.size();
- data = new uchar[data_len];
- ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
- }
- }
+ data = new uchar[data_len];
+ ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
if (!ok) {
delete[] data;
data = nullptr;
data_len = 0;
return false;
}
- fromMM = false;
}
if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) {
if (fromMM) {
@@ -1336,11 +1386,22 @@ private:
uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
bool unmap(uchar *ptr);
void uncompress() const;
- qint64 offset;
+ void mapUncompressed();
+ bool mapUncompressed_sys();
+ void unmapUncompressed_sys();
+ qint64 offset = 0;
QResource resource;
mutable QByteArray uncompressed;
+ bool mustUnmap = false;
+
+ // minimum size for which we'll try to re-open ourselves in mapUncompressed()
+ static constexpr qsizetype RemapCompressedThreshold = 16384;
protected:
- QResourceFileEnginePrivate() : offset(0) { }
+ ~QResourceFileEnginePrivate()
+ {
+ if (mustUnmap)
+ unmapUncompressed_sys();
+ }
};
bool QResourceFileEngine::caseSensitive() const
@@ -1510,10 +1571,10 @@ uint QResourceFileEngine::ownerId(FileOwner) const
return nobodyID;
}
-QDateTime QResourceFileEngine::fileTime(FileTime time) const
+QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const
{
Q_D(const QResourceFileEngine);
- if (time == ModificationTime)
+ if (time == QFile::FileModificationTime)
return d->resource.lastModified();
return QDateTime();
}
@@ -1521,18 +1582,11 @@ QDateTime QResourceFileEngine::fileTime(FileTime time) const
/*!
\internal
*/
-QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
- const QStringList &filterNames)
+QAbstractFileEngine::IteratorUniquePtr
+QResourceFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
{
- return new QResourceFileEngineIterator(filters, filterNames);
-}
-
-/*!
- \internal
-*/
-QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
-{
- return nullptr;
+ return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
}
bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
@@ -1559,21 +1613,27 @@ bool QResourceFileEngine::supportsExtension(Extension extension) const
uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
{
Q_Q(QResourceFileEngine);
- Q_UNUSED(flags);
+ Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression
+ || !uncompressed.isNull(), "QFile::map()",
+ "open() should have uncompressed compressed resources");
qint64 max = resource.uncompressedSize();
qint64 end;
if (offset < 0 || size <= 0 || !resource.isValid() ||
- add_overflow(offset, size, &end) || end > max) {
+ qAddOverflow(offset, size, &end) || end > max) {
q->setError(QFile::UnspecifiedError, QString());
return nullptr;
}
- const uchar *address = resource.data();
- if (resource.compressionAlgorithm() != QResource::NoCompression) {
- uncompress();
- if (uncompressed.isNull())
- return nullptr;
+ const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin());
+ if (!uncompressed.isNull())
+ return const_cast<uchar *>(address) + offset;
+
+ // resource was not compressed
+ address = resource.data();
+ if (flags & QFile::MapPrivateOption) {
+ // We need to provide read-write memory
+ mapUncompressed();
address = reinterpret_cast<const uchar *>(uncompressed.constData());
}
@@ -1594,6 +1654,131 @@ void QResourceFileEnginePrivate::uncompress() const
uncompressed = resource.uncompressedData();
}
+void QResourceFileEnginePrivate::mapUncompressed()
+{
+ Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression);
+ if (!uncompressed.isNull())
+ return; // nothing to do
+
+ if (resource.uncompressedSize() >= RemapCompressedThreshold) {
+ if (mapUncompressed_sys())
+ return;
+ }
+
+ uncompressed = resource.uncompressedData();
+ uncompressed.detach();
+}
+
+#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
+inline bool QResourcePrivate::mayRemapData(const QResource &resource)
+{
+ auto d = resource.d_func();
+
+ // assumptions from load():
+ // - d->related is not empty
+ // - the first item in d->related is the one with our data
+ // by current construction, it's also the only item
+ const QResourceRoot *root = d->related.at(0);
+
+ switch (root->type()) {
+ case QResourceRoot::Resource_Builtin:
+ return true; // always acceptable, memory is read-only
+ case QResourceRoot::Resource_Buffer:
+ return false; // never acceptable, memory is heap
+ case QResourceRoot::Resource_File:
+ break;
+ }
+
+ auto df = static_cast<const QDynamicFileResourceRoot *>(root);
+ return df->wasMemoryMapped();
+}
+#endif
+
+// Returns the page boundaries of where \a location is located in memory.
+static auto mappingBoundaries(const void *location, qsizetype size)
+{
+#ifdef Q_OS_WIN
+ auto getpagesize = [] {
+ SYSTEM_INFO sysinfo;
+ ::GetSystemInfo(&sysinfo);
+ return sysinfo.dwAllocationGranularity;
+ };
+#endif
+ struct R {
+ void *begin;
+ qsizetype size;
+ qptrdiff offset;
+ } r;
+
+ const quintptr pageMask = getpagesize() - 1;
+ quintptr data = quintptr(location);
+ quintptr begin = data & ~pageMask;
+ quintptr end = (data + size + pageMask) & ~pageMask;
+ r.begin = reinterpret_cast<void *>(begin);
+ r.size = end - begin;
+ r.offset = data & pageMask;
+ return r;
+}
+
+bool QResourceFileEnginePrivate::mapUncompressed_sys()
+{
+ auto r = mappingBoundaries(resource.data(), resource.uncompressedSize());
+ void *ptr = nullptr;
+
+#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
+ // Use MREMAP_MAYMOVE to tell the kernel to give us a new address and use
+ // MREMAP_DONTUNMAP (supported since kernel 5.7) to request that it create
+ // a new mapping of the same pages, instead of moving. We can only do that
+ // for pages that are read-only, otherwise the kernel replaces the source
+ // with pages full of nulls.
+ if (!QResourcePrivate::mayRemapData(resource))
+ return false;
+
+ ptr = mremap(r.begin, r.size, r.size, MREMAP_MAYMOVE | MREMAP_DONTUNMAP);
+ if (ptr == MAP_FAILED)
+ return false;
+
+ // Allow writing, which the documentation says we allow. This is safe
+ // because MREMAP_DONTUNMAP only works for private mappings.
+ if (mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
+ munmap(ptr, r.size);
+ return false;
+ }
+#elif defined(Q_OS_DARWIN)
+ mach_port_t self = mach_task_self();
+ vm_address_t addr = 0;
+ vm_address_t mask = 0;
+ bool anywhere = true;
+ bool copy = true;
+ vm_prot_t cur_prot = VM_PROT_READ | VM_PROT_WRITE;
+ vm_prot_t max_prot = VM_PROT_ALL;
+ kern_return_t res = vm_remap(self, &addr, r.size, mask, anywhere,
+ self, vm_address_t(r.begin), copy, &cur_prot,
+ &max_prot, VM_INHERIT_DEFAULT);
+ if (res != KERN_SUCCESS)
+ return false;
+
+ ptr = reinterpret_cast<void *>(addr);
+ if ((max_prot & VM_PROT_WRITE) == 0 || mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
+ munmap(ptr, r.size);
+ return false;
+ }
+#endif
+
+ if (!ptr)
+ return false;
+ const char *newdata = static_cast<char *>(ptr) + r.offset;
+ uncompressed = QByteArray::fromRawData(newdata, resource.uncompressedSize());
+ mustUnmap = true;
+ return true;
+}
+
+void QResourceFileEnginePrivate::unmapUncompressed_sys()
+{
+ auto r = mappingBoundaries(uncompressed.constBegin(), uncompressed.size());
+ QDynamicFileResourceRoot::unmap_sys(r.begin, r.size);
+}
+
#endif // !defined(QT_BOOTSTRAPPED)
QT_END_NAMESPACE
diff --git a/src/corelib/io/qresource_iterator.cpp b/src/corelib/io/qresource_iterator.cpp
index 58bdefdd20..abb61d3b46 100644
--- a/src/corelib/io/qresource_iterator.cpp
+++ b/src/corelib/io/qresource_iterator.cpp
@@ -8,9 +8,10 @@
QT_BEGIN_NAMESPACE
-QResourceFileEngineIterator::QResourceFileEngineIterator(QDir::Filters filters,
+QResourceFileEngineIterator::QResourceFileEngineIterator(const QString &path, QDir::Filters filters,
const QStringList &filterNames)
- : QAbstractFileEngineIterator(filters, filterNames), index(-1)
+ : QAbstractFileEngineIterator(path, filters, filterNames),
+ index(-1)
{
}
@@ -18,15 +19,7 @@ QResourceFileEngineIterator::~QResourceFileEngineIterator()
{
}
-QString QResourceFileEngineIterator::next()
-{
- if (!hasNext())
- return QString();
- ++index;
- return currentFilePath();
-}
-
-bool QResourceFileEngineIterator::hasNext() const
+bool QResourceFileEngineIterator::advance()
{
if (index == -1) {
// Lazy initialization of the iterator
@@ -34,19 +27,34 @@ bool QResourceFileEngineIterator::hasNext() const
if (!resource.isValid())
return false;
- // Initialize and move to the next entry.
+ // Initialize and move to the first entry.
entries = resource.children();
+ if (entries.isEmpty())
+ return false;
+
index = 0;
+ return true;
}
- return index < entries.size();
+ if (index < entries.size() - 1) {
+ ++index;
+ return true;
+ }
+
+ return false;
}
QString QResourceFileEngineIterator::currentFileName() const
{
- if (index <= 0 || index > entries.size())
+ if (index < 0 || index > entries.size())
return QString();
- return entries.at(index - 1);
+ return entries.at(index);
+}
+
+QFileInfo QResourceFileEngineIterator::currentFileInfo() const
+{
+ m_fileInfo = QFileInfo(currentFilePath());
+ return m_fileInfo;
}
QT_END_NAMESPACE
diff --git a/src/corelib/io/qresource_iterator_p.h b/src/corelib/io/qresource_iterator_p.h
index 7bc546e44a..bcbbc46b51 100644
--- a/src/corelib/io/qresource_iterator_p.h
+++ b/src/corelib/io/qresource_iterator_p.h
@@ -24,13 +24,14 @@ class QResourceFileEngineIteratorPrivate;
class QResourceFileEngineIterator : public QAbstractFileEngineIterator
{
public:
- QResourceFileEngineIterator(QDir::Filters filters, const QStringList &filterNames);
+ QResourceFileEngineIterator(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames);
~QResourceFileEngineIterator();
- QString next() override;
- bool hasNext() const override;
+ bool advance() override;
QString currentFileName() const override;
+ QFileInfo currentFileInfo() const override;
private:
mutable QStringList entries;
diff --git a/src/corelib/io/qresource_p.h b/src/corelib/io/qresource_p.h
index 0844e6579a..37fddd7a41 100644
--- a/src/corelib/io/qresource_p.h
+++ b/src/corelib/io/qresource_p.h
@@ -47,10 +47,10 @@ public:
uint ownerId(FileOwner) const override;
- QDateTime fileTime(FileTime time) const override;
+ QDateTime fileTime(QFile::FileTime time) const override;
- Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
- Iterator *endEntryList() override;
+ IteratorUniquePtr beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames) override;
bool extension(Extension extension, const ExtensionOption *option = nullptr, ExtensionReturn *output = nullptr) override;
bool supportsExtension(Extension extension) const override;
diff --git a/src/corelib/io/qsavefile.cpp b/src/corelib/io/qsavefile.cpp
index aa7fb21390..cc59bb3725 100644
--- a/src/corelib/io/qsavefile.cpp
+++ b/src/corelib/io/qsavefile.cpp
@@ -3,7 +3,7 @@
#include "qsavefile.h"
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
#include "qplatformdefs.h"
#include "private/qsavefile_p.h"
@@ -113,10 +113,10 @@ QSaveFile::QSaveFile(const QString &name, QObject *parent)
QSaveFile::~QSaveFile()
{
Q_D(QSaveFile);
- QFileDevice::close();
- if (d->fileEngine) {
+ if (isOpen()) {
+ QFileDevice::close();
+ Q_ASSERT(d->fileEngine);
d->fileEngine->remove();
- d->fileEngine.reset();
}
}
@@ -152,7 +152,7 @@ void QSaveFile::setFileName(const QString &name)
QIODevice::ReadWrite, QIODevice::Append, QIODevice::NewOnly and
QIODevice::ExistingOnly are not supported at the moment.
- \sa QIODevice::OpenMode, setFileName()
+ \sa QIODevice::OpenMode, setFileName(), QT_USE_NODISCARD_FILE_OPEN
*/
bool QSaveFile::open(OpenMode mode)
{
@@ -200,7 +200,7 @@ bool QSaveFile::open(OpenMode mode)
}
auto openDirectly = [&]() {
- d->fileEngine.reset(QAbstractFileEngine::create(d->finalFileName));
+ d->fileEngine = QAbstractFileEngine::create(d->finalFileName);
if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
d->useTemporaryFile = false;
QFileDevice::open(mode);
@@ -298,7 +298,7 @@ bool QSaveFile::commit()
}
QFileDevice::close(); // calls flush()
- const auto fe = std::move(d->fileEngine);
+ const auto &fe = d->fileEngine;
// Sync to disk if possible. Ignore errors (e.g. not supported).
fe->syncToDisk();
@@ -412,4 +412,4 @@ QT_END_NAMESPACE
#include "moc_qsavefile.cpp"
#endif
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
diff --git a/src/corelib/io/qsavefile.h b/src/corelib/io/qsavefile.h
index 9ea4887c3c..4dd712d4b6 100644
--- a/src/corelib/io/qsavefile.h
+++ b/src/corelib/io/qsavefile.h
@@ -6,7 +6,7 @@
#include <QtCore/qglobal.h>
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
#include <QtCore/qfiledevice.h>
#include <QtCore/qstring.h>
@@ -39,7 +39,7 @@ public:
QString fileName() const override;
void setFileName(const QString &name);
- bool open(OpenMode flags) override;
+ QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override;
bool commit();
void cancelWriting();
@@ -62,6 +62,6 @@ private:
QT_END_NAMESPACE
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
#endif // QSAVEFILE_H
diff --git a/src/corelib/io/qsavefile_p.h b/src/corelib/io/qsavefile_p.h
index 50de9e4e68..4d0f40fbb0 100644
--- a/src/corelib/io/qsavefile_p.h
+++ b/src/corelib/io/qsavefile_p.h
@@ -17,7 +17,7 @@
#include <QtCore/qglobal.h>
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
#include "private/qfiledevice_p.h"
@@ -42,6 +42,6 @@ protected:
QT_END_NAMESPACE
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
#endif // QSAVEFILE_P_H
diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp
index 0b28690a4e..6934ca4404 100644
--- a/src/corelib/io/qsettings.cpp
+++ b/src/corelib/io/qsettings.cpp
@@ -129,12 +129,12 @@ bool QConfFile::isWritable() const
{
QFileInfo fileInfo(name);
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
if (fileInfo.exists()) {
#endif
QFile file(name);
return file.open(QFile::ReadWrite);
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
} else {
// Create the directories to the file.
QDir dir(fileInfo.absolutePath());
@@ -878,7 +878,13 @@ QStringList QSettingsPrivate::splitArgs(const QString &s, qsizetype idx)
void QConfFileSettingsPrivate::initFormat()
{
+#if defined(Q_OS_WASM)
+ extension = (format == QSettings::NativeFormat || format == QSettings::WebIndexedDBFormat)
+ ? ".conf"_L1
+ : ".ini"_L1;
+#else
extension = (format == QSettings::NativeFormat) ? ".conf"_L1 : ".ini"_L1;
+#endif
readFunc = nullptr;
writeFunc = nullptr;
#if defined(Q_OS_DARWIN)
@@ -887,7 +893,11 @@ void QConfFileSettingsPrivate::initFormat()
caseSensitivity = IniCaseSensitivity;
#endif
+#if defined Q_OS_WASM
+ if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat) {
+#else
if (format > QSettings::IniFormat) {
+#endif
const auto locker = qt_scoped_lock(settingsGlobalMutex);
const CustomFormatVector *customFormatVector = customFormatVectorFunc();
@@ -905,7 +915,11 @@ void QConfFileSettingsPrivate::initFormat()
void QConfFileSettingsPrivate::initAccess()
{
if (!confFiles.isEmpty()) {
+#if defined Q_OS_WASM
+ if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat) {
+#else
if (format > QSettings::IniFormat) {
+#endif
if (!readFunc)
setStatus(QSettings::AccessError);
}
@@ -943,26 +957,43 @@ static inline int pathHashKey(QSettings::Format format, QSettings::Scope scope)
}
#ifndef Q_OS_WIN
-static QString make_user_path()
+static constexpr QChar sep = u'/';
+
+#if !defined(QSETTINGS_USE_QSTANDARDPATHS) || defined(Q_OS_ANDROID)
+static QString make_user_path_without_qstandard_paths()
{
- static constexpr QChar sep = u'/';
-#ifndef QSETTINGS_USE_QSTANDARDPATHS
- // Non XDG platforms (OS X, iOS, Android...) have used this code path erroneously
- // for some time now. Moving away from that would require migrating existing settings.
QByteArray env = qgetenv("XDG_CONFIG_HOME");
if (env.isEmpty()) {
return QDir::homePath() + "/.config/"_L1;
} else if (env.startsWith('/')) {
return QFile::decodeName(env) + sep;
- } else {
- return QDir::homePath() + sep + QFile::decodeName(env) + sep;
}
+
+ return QDir::homePath() + sep + QFile::decodeName(env) + sep;
+}
+#endif // !QSETTINGS_USE_QSTANDARDPATHS || Q_OS_ANDROID
+
+static QString make_user_path()
+{
+#ifndef QSETTINGS_USE_QSTANDARDPATHS
+ // Non XDG platforms (OS X, iOS, Android...) have used this code path erroneously
+ // for some time now. Moving away from that would require migrating existing settings.
+ // The migration has already been done for Android.
+ return make_user_path_without_qstandard_paths();
#else
- // When using a proper XDG platform, use QStandardPaths rather than the above hand-written code;
- // it makes the use of test mode from unit tests possible.
+
+#ifdef Q_OS_ANDROID
+ // If an old settings path exists, use it instead of creating a new one
+ QString ret = make_user_path_without_qstandard_paths();
+ if (QFile(ret).exists())
+ return ret;
+#endif // Q_OS_ANDROID
+
+ // When using a proper XDG platform or Android platform, use QStandardPaths rather than the
+ // above hand-written code. It makes the use of test mode from unit tests possible.
// Ideally all platforms should use this, but see above for the migration issue.
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + sep;
-#endif
+#endif // !QSETTINGS_USE_QSTANDARDPATHS
}
#endif // !Q_OS_WIN
@@ -1093,9 +1124,7 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format,
confFiles.append(QConfFile::fromName(systemPath.path + orgFile, false));
}
-#ifndef Q_OS_WASM // wasm needs to delay access until after file sync
initAccess();
-#endif
}
QConfFileSettingsPrivate::QConfFileSettingsPrivate(const QString &fileName,
@@ -1227,17 +1256,17 @@ QStringList QConfFileSettingsPrivate::children(const QString &prefix, ChildSpec
else
ensureSectionParsed(confFile, thePrefix);
- auto j = const_cast<const ParsedSettingsMap *>(
- &confFile->originalKeys)->lowerBound( thePrefix);
- while (j != confFile->originalKeys.constEnd() && j.key().startsWith(thePrefix)) {
- if (!confFile->removedKeys.contains(j.key()))
- processChild(QStringView{j.key().originalCaseKey()}.sliced(startPos), spec, result);
- ++j;
+ const auto &originalKeys = confFile->originalKeys;
+ auto i = originalKeys.lowerBound(thePrefix);
+ while (i != originalKeys.end() && i.key().startsWith(thePrefix)) {
+ if (!confFile->removedKeys.contains(i.key()))
+ processChild(QStringView{i.key().originalCaseKey()}.sliced(startPos), spec, result);
+ ++i;
}
- j = const_cast<const ParsedSettingsMap *>(
- &confFile->addedKeys)->lowerBound(thePrefix);
- while (j != confFile->addedKeys.constEnd() && j.key().startsWith(thePrefix)) {
+ const auto &addedKeys = confFile->addedKeys;
+ auto j = addedKeys.lowerBound(thePrefix);
+ while (j != addedKeys.end() && j.key().startsWith(thePrefix)) {
processChild(QStringView{j.key().originalCaseKey()}.sliced(startPos), spec, result);
++j;
}
@@ -1292,7 +1321,11 @@ QString QConfFileSettingsPrivate::fileName() const
bool QConfFileSettingsPrivate::isWritable() const
{
+#if defined(Q_OS_WASM)
+ if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat && !writeFunc)
+#else
if (format > QSettings::IniFormat && !writeFunc)
+#endif
return false;
if (confFiles.isEmpty())
@@ -1327,8 +1360,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
// On android and if it is a content URL put the lock file in a
// writable location to prevent permissions issues and invalid paths.
if (confFile->name.startsWith("content:"_L1))
- lockFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
- + QFileInfo(lockFileName).fileName();
+ lockFileName = make_user_path() + QFileInfo(lockFileName).fileName();
# endif
/*
Use a lockfile in order to protect us against other QSettings instances
@@ -1372,6 +1404,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
*/
if (file.isReadable() && file.size() != 0) {
bool ok = false;
+
+#ifdef Q_OS_WASM
+ if (format == QSettings::WebIndexedDBFormat) {
+ QByteArray data = file.readAll();
+ ok = readIniFile(data, &confFile->unparsedIniSections);
+ } else
+#endif
#ifdef Q_OS_DARWIN
if (format == QSettings::NativeFormat) {
QByteArray data = file.readAll();
@@ -1428,6 +1467,11 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
return;
}
+#ifdef Q_OS_WASM
+ if (format == QSettings::WebIndexedDBFormat) {
+ ok = writeIniFile(sf, mergedKeys);
+ } else
+#endif
#ifdef Q_OS_DARWIN
if (format == QSettings::NativeFormat) {
ok = writePlistFile(sf, mergedKeys);
@@ -1474,6 +1518,8 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
}
}
+namespace SettingsImpl {
+
enum { Space = 0x1, Special = 0x2 };
static const char charTraits[256] =
@@ -1500,10 +1546,15 @@ static const char charTraits[256] =
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
+} // namespace SettingsImpl
+
+using SettingsImpl::charTraits;
+
bool QConfFileSettingsPrivate::readIniLine(QByteArrayView data, qsizetype &dataPos,
qsizetype &lineStart, qsizetype &lineLen,
qsizetype &equalsPos)
{
+ using namespace SettingsImpl;
qsizetype dataLen = data.size();
bool inQuotes = false;
@@ -2077,10 +2128,6 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
as QString. The numeric value can be recovered using \l QString::toInt(), \l
QString::toDouble() and related functions.
- The \l{tools/settingseditor}{Settings Editor} example lets you
- experiment with different settings location and with fallbacks
- turned on or off.
-
\section1 Restoring the State of a GUI Application
QSettings is often used to store the state of a GUI
@@ -2106,9 +2153,6 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
\codeline
\snippet settings/settings.cpp 21
- See the \l{mainwindows/application}{Application} example for a
- self-contained example that uses QSettings.
-
\section1 Accessing Settings from Multiple Threads or Processes Simultaneously
QSettings is \l{reentrant}. This means that you can use
@@ -2150,8 +2194,8 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
following files are used by default:
\list 1
- \li \c{$HOME/.config/MySoft/Star Runner.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.conf})
- \li \c{$HOME/.config/MySoft.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.conf})
+ \li \c{$HOME/.config/MySoft/Star Runner.conf}
+ \li \c{$HOME/.config/MySoft.conf}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.conf}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.conf}
\endlist
@@ -2188,8 +2232,8 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
used on Unix, \macos, and iOS:
\list 1
- \li \c{$HOME/.config/MySoft/Star Runner.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.ini})
- \li \c{$HOME/.config/MySoft.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.ini})
+ \li \c{$HOME/.config/MySoft/Star Runner.ini}
+ \li \c{$HOME/.config/MySoft.ini}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.ini}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.ini}
\endlist
@@ -2324,7 +2368,7 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
\endlist
- \sa QVariant, QSessionManager, {Settings Editor Example}, {Qt Widgets - Application Example}
+ \sa QVariant, QSessionManager
*/
/*! \enum QSettings::Status
@@ -2362,6 +2406,16 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
lose the distinction between numeric data and the
strings used to encode them, so values written as
numbers shall be read back as QString.
+ \value WebLocalStorageFormat
+ WASM only: Store the settings in window.localStorage for the current
+ origin. If cookies are not allowed, this falls back to the INI format.
+ This provides up to 5MiB storage per origin, but access to it is
+ synchronous and JSPI is not required.
+ \value WebIndexedDBFormat
+ WASM only: Store the settings in an Indexed DB for the current
+ origin. If cookies are not allowed, this falls back to the INI format.
+ This requires JSPI, but provides more storage than
+ WebLocalStorageFormat.
\value InvalidFormat Special value returned by registerFormat().
\omitvalue CustomFormat1
@@ -3360,8 +3414,6 @@ QSettings::Format QSettings::defaultFormat()
\row \li SystemScope \li \c FOLDERID_ProgramData
\row \li{1,2} Unix \li{1,2} NativeFormat, IniFormat \li UserScope \li \c $HOME/.config
\row \li SystemScope \li \c /etc/xdg
- \row \li{1,2} Qt for Embedded Linux \li{1,2} NativeFormat, IniFormat \li UserScope \li \c $HOME/Settings
- \row \li SystemScope \li \c /etc/xdg
\row \li{1,2} \macos and iOS \li{1,2} IniFormat \li UserScope \li \c $HOME/.config
\row \li SystemScope \li \c /etc/xdg
\endtable
diff --git a/src/corelib/io/qsettings.h b/src/corelib/io/qsettings.h
index 86b55ea241..8bc73eb016 100644
--- a/src/corelib/io/qsettings.h
+++ b/src/corelib/io/qsettings.h
@@ -46,18 +46,17 @@ public:
#endif
enum Format {
- NativeFormat,
- IniFormat,
+ NativeFormat = 0,
+ IniFormat = 1,
#if defined(Q_OS_WIN) || defined(Q_QDOC)
- Registry32Format,
- Registry64Format,
+ Registry32Format = 2,
+ Registry64Format = 3,
#endif
-#if defined(Q_OS_WASM)
- // FIXME: add public API in next minor release.
- // WebLocalStorageFormat (IniFormat + 1)
- // WebIDBSFormat (IniFormat + 2)
+#if defined(Q_OS_WASM) || defined(Q_QDOC)
+ WebLocalStorageFormat = 4,
+ WebIndexedDBFormat = 5,
#endif
InvalidFormat = 16,
diff --git a/src/corelib/io/qsettings_p.h b/src/corelib/io/qsettings_p.h
index d79c10e643..4229abd874 100644
--- a/src/corelib/io/qsettings_p.h
+++ b/src/corelib/io/qsettings_p.h
@@ -243,6 +243,9 @@ public:
qsizetype &lineStart, qsizetype &lineLen,
qsizetype &equalsPos);
+protected:
+ const QList<QConfFile *> &getConfFiles() const { return confFiles; }
+
private:
void initFormat();
virtual void initAccess();
diff --git a/src/corelib/io/qsettings_wasm.cpp b/src/corelib/io/qsettings_wasm.cpp
index 15ab688abe..7d80ff82d3 100644
--- a/src/corelib/io/qsettings_wasm.cpp
+++ b/src/corelib/io/qsettings_wasm.cpp
@@ -10,19 +10,33 @@
#include <QFile>
#endif // QT_NO_QOBJECT
#include <QDebug>
+#include <QtCore/private/qstdweb_p.h>
#include <QFileInfo>
#include <QDir>
#include <QList>
+#include <QSet>
#include <emscripten.h>
-#include <emscripten/val.h>
+# include <emscripten/proxying.h>
+# include <emscripten/threading.h>
+# include <emscripten/val.h>
QT_BEGIN_NAMESPACE
using emscripten::val;
using namespace Qt::StringLiterals;
+namespace {
+QStringView keyNameFromPrefixedStorageName(QStringView prefix, QStringView prefixedStorageName)
+{
+ // Return the key slice after m_keyPrefix, or an empty string view if no match
+ if (!prefixedStorageName.startsWith(prefix))
+ return QStringView();
+ return prefixedStorageName.sliced(prefix.length());
+}
+} // namespace
+
//
// Native settings implementation for WebAssembly using window.localStorage
// as the storage backend. localStorage is a key-value store with a synchronous
@@ -33,6 +47,7 @@ class QWasmLocalStorageSettingsPrivate final : public QSettingsPrivate
public:
QWasmLocalStorageSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application);
+ ~QWasmLocalStorageSettingsPrivate() final = default;
void remove(const QString &key) final;
void set(const QString &key, const QVariant &value) final;
@@ -45,10 +60,7 @@ public:
QString fileName() const final;
private:
- QString prependStoragePrefix(const QString &key) const;
- QStringView removeStoragePrefix(QStringView key) const;
- val m_localStorage = val::global("window")["localStorage"];
- QString m_keyPrefix;
+ QStringList m_keyPrefixes;
};
QWasmLocalStorageSettingsPrivate::QWasmLocalStorageSettingsPrivate(QSettings::Scope scope,
@@ -56,88 +68,147 @@ QWasmLocalStorageSettingsPrivate::QWasmLocalStorageSettingsPrivate(QSettings::Sc
const QString &application)
: QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
{
+ if (organization.isEmpty()) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
+
// The key prefix contians "qt" to separate Qt keys from other keys on localStorage, a
// version tag to allow for making changes to the key format in the future, the org
// and app names.
//
// User code could could create separate settings object with different org and app names,
- // and would expect them to have separate settings. Also, different webassembly instanaces
+ // and would expect them to have separate settings. Also, different webassembly instances
// on the page could write to the same window.localStorage. Add the org and app name
- // to the key prefix to differentiate, even if that leads to keys with redundant sectons
+ // to the key prefix to differentiate, even if that leads to keys with redundant sections
// for the common case of a single org and app name.
+ //
+ // Also, the common Qt mechanism for user/system scope and all-application settings are
+ // implemented, using different prefixes.
+ const QString allAppsSetting = QStringLiteral("all-apps");
+ const QString systemSetting = QStringLiteral("sys-tem");
+
const QLatin1String separator("-");
const QLatin1String doubleSeparator("--");
const QString escapedOrganization = QString(organization).replace(separator, doubleSeparator);
const QString escapedApplication = QString(application).replace(separator, doubleSeparator);
- const QLatin1String prefix("qt-v0-");
- m_keyPrefix.reserve(prefix.length() + escapedOrganization.length() +
- escapedApplication.length() + separator.length() * 2);
- m_keyPrefix = prefix + escapedOrganization + separator + escapedApplication + separator;
+ const QString prefix = "qt-v0-" + escapedOrganization + separator;
+ if (scope == QSettings::Scope::UserScope) {
+ if (!escapedApplication.isEmpty())
+ m_keyPrefixes.push_back(prefix + escapedApplication + separator);
+ m_keyPrefixes.push_back(prefix + allAppsSetting + separator);
+ }
+ if (!escapedApplication.isEmpty()) {
+ m_keyPrefixes.push_back(prefix + escapedApplication + separator + systemSetting
+ + separator);
+ }
+ m_keyPrefixes.push_back(prefix + allAppsSetting + separator + systemSetting + separator);
}
void QWasmLocalStorageSettingsPrivate::remove(const QString &key)
{
- const std::string keyString = prependStoragePrefix(key).toStdString();
- m_localStorage.call<val>("removeItem", keyString);
+ const std::string removed = QString(m_keyPrefixes.first() + key).toStdString();
+
+ qstdweb::runTaskOnMainThread<void>([this, &removed, &key]() {
+ std::vector<std::string> children = { removed };
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ const QString storedKeyWithPrefix = QString::fromStdString(
+ val::global("window")["localStorage"].call<val>("key", i).as<std::string>());
+
+ const QStringView storedKey = keyNameFromPrefixedStorageName(
+ m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
+ if (storedKey.isEmpty() || !storedKey.startsWith(key))
+ continue;
+
+ children.push_back(storedKeyWithPrefix.toStdString());
+ }
+
+ for (const auto &child : children)
+ val::global("window")["localStorage"].call<val>("removeItem", child);
+ });
}
void QWasmLocalStorageSettingsPrivate::set(const QString &key, const QVariant &value)
{
- const std::string keyString = prependStoragePrefix(key).toStdString();
- const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
- m_localStorage.call<void>("setItem", keyString, valueString);
+ qstdweb::runTaskOnMainThread<void>([this, &key, &value]() {
+ const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
+ const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
+ val::global("window")["localStorage"].call<void>("setItem", keyString, valueString);
+ });
}
std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
{
- const std::string keyString = prependStoragePrefix(key).toStdString();
- const emscripten::val value = m_localStorage.call<val>("getItem", keyString);
- if (value.isNull())
- return std::nullopt;
- const QString valueString = QString::fromStdString(value.as<std::string>());
- return QSettingsPrivate::stringToVariant(valueString);
+ return qstdweb::runTaskOnMainThread<std::optional<QVariant>>(
+ [this, &key]() -> std::optional<QVariant> {
+ for (const auto &prefix : m_keyPrefixes) {
+ const std::string keyString = QString(prefix + key).toStdString();
+ const emscripten::val value =
+ val::global("window")["localStorage"].call<val>("getItem", keyString);
+ if (!value.isNull()) {
+ return QSettingsPrivate::stringToVariant(
+ QString::fromStdString(value.as<std::string>()));
+ }
+ if (!fallbacks) {
+ return std::nullopt;
+ }
+ }
+ return std::nullopt;
+ });
}
QStringList QWasmLocalStorageSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
{
- // Loop through all keys on window.localStorage, return Qt keys belonging to
- // this application, with the correct prefix, and according to ChildSpec.
- QStringList children;
- const int length = m_localStorage["length"].as<int>();
- for (int i = 0; i < length; ++i) {
- const QString keyString =
- QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
-
- const QStringView key = removeStoragePrefix(QStringView(keyString));
- if (key.isEmpty())
- continue;
- if (!key.startsWith(prefix))
- continue;
-
- QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
- }
-
- return children;
+ return qstdweb::runTaskOnMainThread<QStringList>([this, &prefix, &spec]() -> QStringList {
+ QSet<QString> nodes;
+ // Loop through all keys on window.localStorage, return Qt keys belonging to
+ // this application, with the correct prefix, and according to ChildSpec.
+ QStringList children;
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ for (const auto &storagePrefix : m_keyPrefixes) {
+ const QString keyString =
+ QString::fromStdString(val::global("window")["localStorage"]
+ .call<val>("key", i)
+ .as<std::string>());
+
+ const QStringView key =
+ keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
+ if (!key.isEmpty() && key.startsWith(prefix)) {
+ QStringList children;
+ QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
+ if (!children.isEmpty())
+ nodes.insert(children.first());
+ }
+ if (!fallbacks)
+ break;
+ }
+ }
+
+ return QStringList(nodes.begin(), nodes.end());
+ });
}
void QWasmLocalStorageSettingsPrivate::clear()
{
- // Get all Qt keys from window.localStorage
- const int length = m_localStorage["length"].as<int>();
- std::vector<std::string> keys;
- keys.reserve(length);
- for (int i = 0; i < length; ++i) {
- std::string key = (m_localStorage.call<val>("key", i).as<std::string>());
- keys.push_back(std::move(key));
- }
-
- // Remove all Qt keys. Note that localStorage does not guarantee a stable
- // iteration order when the storage is mutated, which is why removal is done
- // in a second step after getting all keys.
- for (std::string key: keys) {
- if (removeStoragePrefix(QString::fromStdString(key)).isEmpty() == false)
- m_localStorage.call<val>("removeItem", key);
- }
+ qstdweb::runTaskOnMainThread<void>([this]() {
+ // Get all Qt keys from window.localStorage
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ QStringList keys;
+ keys.reserve(length);
+ for (int i = 0; i < length; ++i)
+ keys.append(QString::fromStdString(
+ (val::global("window")["localStorage"].call<val>("key", i).as<std::string>())));
+
+ // Remove all Qt keys. Note that localStorage does not guarantee a stable
+ // iteration order when the storage is mutated, which is why removal is done
+ // in a second step after getting all keys.
+ for (const QString &key : keys) {
+ if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
+ val::global("window")["localStorage"].call<val>("removeItem", key.toStdString());
+ }
+ });
}
void QWasmLocalStorageSettingsPrivate::sync() { }
@@ -154,19 +225,6 @@ QString QWasmLocalStorageSettingsPrivate::fileName() const
return QString();
}
-QString QWasmLocalStorageSettingsPrivate::prependStoragePrefix(const QString &key) const
-{
- return m_keyPrefix + key;
-}
-
-QStringView QWasmLocalStorageSettingsPrivate::removeStoragePrefix(QStringView key) const
-{
- // Return the key slice after m_keyPrefix, or an empty string view if no match
- if (!key.startsWith(m_keyPrefix))
- return QStringView();
- return key.sliced(m_keyPrefix.length());
-}
-
//
// Native settings implementation for WebAssembly using the indexed database as
// the storage backend
@@ -177,225 +235,168 @@ public:
QWasmIDBSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application);
~QWasmIDBSettingsPrivate();
- static QWasmIDBSettingsPrivate *get(void *userData);
- std::optional<QVariant> get(const QString &key) const override;
- QStringList children(const QString &prefix, ChildSpec spec) const override;
void clear() override;
void sync() override;
- void flush() override;
- bool isWritable() const override;
-
- void syncToLocal(const char *data, int size);
- void loadLocal(const QByteArray &filename);
- void setReady();
- void initAccess() override;
private:
+ bool writeSettingsToTemporaryFile(const QString &fileName, void *dataPtr, int size);
+ void loadIndexedDBFiles();
+
+
QString databaseName;
QString id;
- static QList<QWasmIDBSettingsPrivate *> liveSettings;
};
-QList<QWasmIDBSettingsPrivate *> QWasmIDBSettingsPrivate::liveSettings;
-static bool isReadReady = false;
-
-static void QWasmIDBSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
-{
- QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData);
- if (!settings)
- return;
-
- QFile file(settings->fileName());
- QFileInfo fileInfo(settings->fileName());
- QDir dir(fileInfo.path());
- if (!dir.exists())
- dir.mkpath(fileInfo.path());
-
- if (file.open(QFile::WriteOnly)) {
- file.write(reinterpret_cast<char *>(dataPtr), size);
- file.close();
- settings->setReady();
- }
-}
-
-static void QWasmIDBSettingsPrivate_onError(void *userData)
-{
- if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData))
- settings->setStatus(QSettings::AccessError);
-}
-
-static void QWasmIDBSettingsPrivate_onStore(void *userData)
-{
- if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData))
- settings->setStatus(QSettings::NoError);
-}
-
-static void QWasmIDBSettingsPrivate_onCheck(void *userData, int exists)
-{
- if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData)) {
- if (exists)
- settings->loadLocal(settings->fileName().toLocal8Bit());
- else
- settings->setReady();
- }
-}
+constexpr char DbName[] = "/home/web_user";
QWasmIDBSettingsPrivate::QWasmIDBSettingsPrivate(QSettings::Scope scope,
const QString &organization,
const QString &application)
- : QConfFileSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
+ : QConfFileSettingsPrivate(QSettings::WebIndexedDBFormat, scope, organization, application)
{
- liveSettings.push_back(this);
+ Q_ASSERT_X(qstdweb::haveJspi(), Q_FUNC_INFO, "QWasmIDBSettingsPrivate needs JSPI to work");
+
+ if (organization.isEmpty()) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
- setStatus(QSettings::AccessError); // access error until sandbox gets loaded
databaseName = organization;
id = application;
- emscripten_idb_async_exists("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onCheck,
- QWasmIDBSettingsPrivate_onError);
-}
-
-QWasmIDBSettingsPrivate::~QWasmIDBSettingsPrivate()
-{
- liveSettings.removeAll(this);
-}
+ loadIndexedDBFiles();
-QWasmIDBSettingsPrivate *QWasmIDBSettingsPrivate::get(void *userData)
-{
- if (QWasmIDBSettingsPrivate::liveSettings.contains(userData))
- return reinterpret_cast<QWasmIDBSettingsPrivate *>(userData);
- return nullptr;
+ QConfFileSettingsPrivate::initAccess();
}
-void QWasmIDBSettingsPrivate::initAccess()
-{
- if (isReadReady)
- QConfFileSettingsPrivate::initAccess();
-}
+QWasmIDBSettingsPrivate::~QWasmIDBSettingsPrivate() = default;
-std::optional<QVariant> QWasmIDBSettingsPrivate::get(const QString &key) const
+bool QWasmIDBSettingsPrivate::writeSettingsToTemporaryFile(const QString &fileName, void *dataPtr,
+ int size)
{
- if (isReadReady)
- return QConfFileSettingsPrivate::get(key);
+ QFile file(fileName);
+ QFileInfo fileInfo(fileName);
+ QDir dir(fileInfo.path());
+ if (!dir.exists())
+ dir.mkpath(fileInfo.path());
- return std::nullopt;
-}
+ if (!file.open(QFile::WriteOnly))
+ return false;
-QStringList QWasmIDBSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
-{
- return QConfFileSettingsPrivate::children(prefix, spec);
+ return size == file.write(reinterpret_cast<char *>(dataPtr), size);
}
void QWasmIDBSettingsPrivate::clear()
{
QConfFileSettingsPrivate::clear();
- emscripten_idb_async_delete("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onStore,
- QWasmIDBSettingsPrivate_onError);
+
+ int error = 0;
+ emscripten_idb_delete(DbName, fileName().toLocal8Bit(), &error);
+ setStatus(!!error ? QSettings::AccessError : QSettings::NoError);
}
void QWasmIDBSettingsPrivate::sync()
{
+ // Reload the files, in case there were any changes in IndexedDB, and flush them to disk.
+ // Thanks to this, QConfFileSettingsPrivate::sync will handle key merging correctly.
+ loadIndexedDBFiles();
+
QConfFileSettingsPrivate::sync();
QFile file(fileName());
if (file.open(QFile::ReadOnly)) {
QByteArray dataPointer = file.readAll();
- emscripten_idb_async_store("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void *>(dataPointer.data()),
- dataPointer.length(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onStore,
- QWasmIDBSettingsPrivate_onError);
+ int error = 0;
+ emscripten_idb_store(DbName, fileName().toLocal8Bit(),
+ reinterpret_cast<void *>(dataPointer.data()), dataPointer.length(),
+ &error);
+ setStatus(!!error ? QSettings::AccessError : QSettings::NoError);
}
}
-void QWasmIDBSettingsPrivate::flush()
-{
- sync();
-}
-
-bool QWasmIDBSettingsPrivate::isWritable() const
+void QWasmIDBSettingsPrivate::loadIndexedDBFiles()
{
- return isReadReady && QConfFileSettingsPrivate::isWritable();
-}
-
-void QWasmIDBSettingsPrivate::syncToLocal(const char *data, int size)
-{
- QFile file(fileName());
-
- if (file.open(QFile::WriteOnly)) {
- file.write(data, size + 1);
- QByteArray data = file.readAll();
-
- emscripten_idb_async_store("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void *>(data.data()),
- data.length(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onStore,
- QWasmIDBSettingsPrivate_onError);
- setReady();
+ for (const auto *confFile : getConfFiles()) {
+ int exists = 0;
+ int error = 0;
+ emscripten_idb_exists(DbName, confFile->name.toLocal8Bit(), &exists, &error);
+ if (error) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
+ if (exists) {
+ void *contents;
+ int size;
+ emscripten_idb_load(DbName, confFile->name.toLocal8Bit(), &contents, &size, &error);
+ if (error || !writeSettingsToTemporaryFile(confFile->name, contents, size)) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
+ }
}
}
-void QWasmIDBSettingsPrivate::loadLocal(const QByteArray &filename)
-{
- emscripten_idb_async_load("/home/web_user",
- filename.data(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onLoad,
- QWasmIDBSettingsPrivate_onError);
-}
-
-void QWasmIDBSettingsPrivate::setReady()
-{
- isReadReady = true;
- setStatus(QSettings::NoError);
- QConfFileSettingsPrivate::initAccess();
-}
-
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
const QString &organization, const QString &application)
{
- const auto WebLocalStorageFormat = QSettings::IniFormat + 1;
- const auto WebIdbFormat = QSettings::IniFormat + 2;
-
// Make WebLocalStorageFormat the default native format
if (format == QSettings::NativeFormat)
- format = QSettings::Format(WebLocalStorageFormat);
+ format = QSettings::WebLocalStorageFormat;
// Check if cookies are enabled (required for using persistent storage)
- const bool cookiesEnabled = val::global("navigator")["cookieEnabled"].as<bool>();
- constexpr QLatin1StringView cookiesWarningMessage
- ("QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
- if (format == WebLocalStorageFormat && !cookiesEnabled) {
- qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
- format = QSettings::IniFormat;
- } else if (format == WebIdbFormat && !cookiesEnabled) {
- qWarning() << cookiesWarningMessage.arg("WebIdbFormat");
+
+ const bool cookiesEnabled = qstdweb::runTaskOnMainThread<bool>(
+ []() { return val::global("navigator")["cookieEnabled"].as<bool>(); });
+
+ constexpr QLatin1StringView cookiesWarningMessage(
+ "QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
+ if (!cookiesEnabled) {
+ if (format == QSettings::WebLocalStorageFormat) {
+ qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
+ format = QSettings::IniFormat;
+ } else if (format == QSettings::WebIndexedDBFormat) {
+ qWarning() << cookiesWarningMessage.arg("WebIndexedDBFormat");
+ format = QSettings::IniFormat;
+ }
+ }
+ if (format == QSettings::WebIndexedDBFormat && !qstdweb::haveJspi()) {
+ qWarning() << "QSettings::WebIndexedDBFormat requires JSPI, falling back to IniFormat with "
+ "temporary file";
format = QSettings::IniFormat;
}
// Create settings backend according to selected format
- if (format == WebLocalStorageFormat) {
+ switch (format) {
+ case QSettings::Format::WebLocalStorageFormat:
return new QWasmLocalStorageSettingsPrivate(scope, organization, application);
- } else if (format == WebIdbFormat) {
+ case QSettings::Format::WebIndexedDBFormat:
return new QWasmIDBSettingsPrivate(scope, organization, application);
- } else if (format == QSettings::IniFormat) {
+ case QSettings::Format::IniFormat:
+ case QSettings::Format::CustomFormat1:
+ case QSettings::Format::CustomFormat2:
+ case QSettings::Format::CustomFormat3:
+ case QSettings::Format::CustomFormat4:
+ case QSettings::Format::CustomFormat5:
+ case QSettings::Format::CustomFormat6:
+ case QSettings::Format::CustomFormat7:
+ case QSettings::Format::CustomFormat8:
+ case QSettings::Format::CustomFormat9:
+ case QSettings::Format::CustomFormat10:
+ case QSettings::Format::CustomFormat11:
+ case QSettings::Format::CustomFormat12:
+ case QSettings::Format::CustomFormat13:
+ case QSettings::Format::CustomFormat14:
+ case QSettings::Format::CustomFormat15:
+ case QSettings::Format::CustomFormat16:
return new QConfFileSettingsPrivate(format, scope, organization, application);
+ case QSettings::Format::InvalidFormat:
+ return nullptr;
+ case QSettings::Format::NativeFormat:
+ Q_UNREACHABLE();
+ break;
}
-
- qWarning() << "Unsupported settings format" << format;
- return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/corelib/io/qstandardpaths.cpp b/src/corelib/io/qstandardpaths.cpp
index 9a14ae0717..792721f50d 100644
--- a/src/corelib/io/qstandardpaths.cpp
+++ b/src/corelib/io/qstandardpaths.cpp
@@ -126,6 +126,12 @@ using namespace Qt::StringLiterals;
template files can be stored. This is a generic value. Note that the returned path may be
empty if the system has no concept of a templates location.
This enum value was added in Qt 6.4.
+ \value [since 6.7] StateLocation Returns a directory location where user-specific application
+ state data files should be written. This is an application-specific directory,
+ and the returned path is never empty.
+ \value [since 6.7] GenericStateLocation Returns a directory location where shared state data files
+ across applications should be written. This value might be generic or application-specific,
+ but the returned path is never empty.
The following table gives examples of paths on different operating systems.
The first path is the writable path (unless noted). Other, additional
@@ -166,6 +172,9 @@ using namespace Qt::StringLiterals;
\row \li CacheLocation
\li "~/Library/Caches/<APPNAME>", "/Library/Caches/<APPNAME>"
\li "C:/Users/<USER>/AppData/Local/<APPNAME>/cache"
+ \row \li StateLocation
+ \li "~/Library/Preferences/<APPNAME>/State"
+ \li "C:/Users/<USER>/AppData/Local/<APPNAME>/State", "C:/ProgramData/<APPNAME>/State"
\row \li GenericDataLocation
\li "~/Library/Application Support", "/Library/Application Support"
\li "C:/Users/<USER>/AppData/Local", "C:/ProgramData", "<APPDIR>", "<APPDIR>/data"
@@ -184,6 +193,9 @@ using namespace Qt::StringLiterals;
\row \li GenericCacheLocation
\li "~/Library/Caches", "/Library/Caches"
\li "C:/Users/<USER>/AppData/Local/cache"
+ \row \li GenericStateLocation
+ \li "~/Library/Preferences/State"
+ \li "C:/Users/<USER>/AppData/Local/State", "C:/ProgramData/State"
\row \li AppDataLocation
\li "~/Library/Application Support/<APPNAME>", "/Library/Application Support/<APPNAME>". "<APPDIR>/../Resources"
\li "C:/Users/<USER>/AppData/Roaming/<APPNAME>", "C:/ProgramData/<APPNAME>", "<APPDIR>", "<APPDIR>/data", "<APPDIR>/data/<APPNAME>"
@@ -222,6 +234,8 @@ using namespace Qt::StringLiterals;
\li "~/.local/share/<APPNAME>", "/usr/local/share/<APPNAME>", "/usr/share/<APPNAME>"
\row \li CacheLocation
\li "~/.cache/<APPNAME>"
+ \row \li StateLocation
+ \li "~/.local/state/<APPNAME>"
\row \li GenericDataLocation
\li "~/.local/share", "/usr/local/share", "/usr/share"
\row \li RuntimeLocation
@@ -234,6 +248,8 @@ using namespace Qt::StringLiterals;
\li "~/Downloads"
\row \li GenericCacheLocation
\li "~/.cache"
+ \row \li GenericStateLocation
+ \li "~/.local/state"
\row \li AppDataLocation
\li "~/.local/share/<APPNAME>", "/usr/local/share/<APPNAME>", "/usr/share/<APPNAME>"
\row \li AppConfigLocation
@@ -279,6 +295,10 @@ using namespace Qt::StringLiterals;
\row \li CacheLocation
\li "<APPROOT>/cache", "<USER>/<APPNAME>/cache"
\li "<APPROOT>/Library/Caches"
+ \row \li StateLocation
+ \li "<APPROOT>/files/state"
+ \row \li GenericStateLocation (there is shared state)
+ \li "<APPROOT>/files/state"
\row \li GenericDataLocation
\li "<USER>" [*] or "<USER>/<APPNAME>/files"
\li "<APPROOT>/Library/Application Support"
@@ -520,6 +540,8 @@ QString QStandardPaths::findExecutable(const QString &executableName, const QStr
}
/*!
+ \fn QString QStandardPaths::displayName(StandardLocation type)
+
\include standardpath/functiondocs.qdocinc displayName
*/
@@ -549,6 +571,8 @@ QString QStandardPaths::displayName(StandardLocation type)
return QCoreApplication::translate("QStandardPaths", "Application Data");
case CacheLocation:
return QCoreApplication::translate("QStandardPaths", "Cache");
+ case StateLocation:
+ return QCoreApplication::translate("QStandardPaths", "State");
case GenericDataLocation:
return QCoreApplication::translate("QStandardPaths", "Shared Data");
case RuntimeLocation:
@@ -559,6 +583,8 @@ QString QStandardPaths::displayName(StandardLocation type)
return QCoreApplication::translate("QStandardPaths", "Shared Configuration");
case GenericCacheLocation:
return QCoreApplication::translate("QStandardPaths", "Shared Cache");
+ case GenericStateLocation:
+ return QCoreApplication::translate("QStandardPaths", "Shared State");
case DownloadLocation:
return QCoreApplication::translate("QStandardPaths", "Download");
case AppDataLocation:
diff --git a/src/corelib/io/qstandardpaths.h b/src/corelib/io/qstandardpaths.h
index ca1e37d92c..56aa2b100c 100644
--- a/src/corelib/io/qstandardpaths.h
+++ b/src/corelib/io/qstandardpaths.h
@@ -38,7 +38,9 @@ public:
AppDataLocation,
AppConfigLocation,
PublicShareLocation,
- TemplatesLocation
+ TemplatesLocation,
+ StateLocation,
+ GenericStateLocation,
};
Q_ENUM(StandardLocation)
diff --git a/src/corelib/io/qstandardpaths_android.cpp b/src/corelib/io/qstandardpaths_android.cpp
index e058f379c2..f39b6855b6 100644
--- a/src/corelib/io/qstandardpaths_android.cpp
+++ b/src/corelib/io/qstandardpaths_android.cpp
@@ -13,7 +13,7 @@
QT_BEGIN_NAMESPACE
Q_DECLARE_JNI_CLASS(Environment, "android/os/Environment");
-Q_DECLARE_JNI_TYPE(File, "Ljava/io/File;");
+Q_DECLARE_JNI_CLASS(File, "java/io/File");
using namespace QNativeInterface;
using namespace Qt::StringLiterals;
@@ -195,6 +195,9 @@ QString QStandardPaths::writableLocation(StandardLocation type)
case QStandardPaths::ConfigLocation:
case QStandardPaths::AppConfigLocation:
return getFilesDir() + testDir() + "/settings"_L1;
+ case QStandardPaths::StateLocation:
+ case QStandardPaths::GenericStateLocation:
+ return getFilesDir() + testDir() + "/state"_L1;
case QStandardPaths::GenericDataLocation:
{
return QAndroidApplication::sdkVersion() >= 30 ?
diff --git a/src/corelib/io/qstandardpaths_haiku.cpp b/src/corelib/io/qstandardpaths_haiku.cpp
index 6122e5f6f9..93eba134f3 100644
--- a/src/corelib/io/qstandardpaths_haiku.cpp
+++ b/src/corelib/io/qstandardpaths_haiku.cpp
@@ -120,8 +120,10 @@ QString QStandardPaths::writableLocation(StandardLocation type)
return haikuAppStandardPath(B_USER_CACHE_DIRECTORY);
case GenericCacheLocation:
return haikuStandardPath(B_USER_CACHE_DIRECTORY);
- case ConfigLocation: // fall through
+ case ConfigLocation:
case AppConfigLocation:
+ case StateLocation:
+ case GenericStateLocation:
return haikuAppStandardPath(B_USER_SETTINGS_DIRECTORY);
case GenericConfigLocation:
return haikuStandardPath(B_USER_SETTINGS_DIRECTORY);
diff --git a/src/corelib/io/qstandardpaths_mac.mm b/src/corelib/io/qstandardpaths_mac.mm
index 5a41ae8e92..2acbe92736 100644
--- a/src/corelib/io/qstandardpaths_mac.mm
+++ b/src/corelib/io/qstandardpaths_mac.mm
@@ -120,6 +120,12 @@ static QString baseWritableLocation(QStandardPaths::StandardLocation type,
case QStandardPaths::AppConfigLocation:
path = pathForDirectory(NSLibraryDirectory, mask) + "/Preferences"_L1;
break;
+ case QStandardPaths::StateLocation:
+ if (appendOrgAndApp) { break; }
+ Q_FALLTHROUGH();
+ case QStandardPaths::GenericStateLocation:
+ path = pathForDirectory(NSLibraryDirectory, mask) + "/Preferences/State"_L1;
+ break;
default:
path = pathForDirectory(dir, mask);
break;
@@ -133,6 +139,11 @@ static QString baseWritableLocation(QStandardPaths::StandardLocation type,
case QStandardPaths::CacheLocation:
appendOrganizationAndApp(path);
break;
+ case QStandardPaths::StateLocation:
+ path = pathForDirectory(NSLibraryDirectory, mask) + "/Preferences"_L1;
+ appendOrganizationAndApp(path);
+ path += "/State"_L1;
+ break;
default:
break;
}
diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp
index 64d8a3fa49..e38f670895 100644
--- a/src/corelib/io/qstandardpaths_unix.cpp
+++ b/src/corelib/io/qstandardpaths_unix.cpp
@@ -196,6 +196,25 @@ QString QStandardPaths::writableLocation(StandardLocation type)
appendOrganizationAndApp(xdgCacheHome);
return xdgCacheHome;
}
+ case StateLocation:
+ case GenericStateLocation:
+ {
+ QString xdgStateHome;
+ if (isTestModeEnabled()) {
+ xdgStateHome = QDir::homePath() + "/.qttest/state"_L1;
+ } else {
+ // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html
+ xdgStateHome = QFile::decodeName(qgetenv("XDG_STATE_HOME"));
+ if (!xdgStateHome.startsWith(u'/'))
+ xdgStateHome.clear(); // spec says relative paths should be ignored
+
+ if (xdgStateHome.isEmpty())
+ xdgStateHome = QDir::homePath() + "/.local/state"_L1;
+ }
+ if (type == QStandardPaths::StateLocation)
+ appendOrganizationAndApp(xdgStateHome);
+ return xdgStateHome;
+ }
case AppDataLocation:
case AppLocalDataLocation:
case GenericDataLocation:
diff --git a/src/corelib/io/qstandardpaths_win.cpp b/src/corelib/io/qstandardpaths_win.cpp
index 13b8fe224a..805ce65a5a 100644
--- a/src/corelib/io/qstandardpaths_win.cpp
+++ b/src/corelib/io/qstandardpaths_win.cpp
@@ -105,8 +105,10 @@ static GUID writableSpecialFolderId(QStandardPaths::StandardLocation type)
FOLDERID_LocalAppData, // AppConfigLocation ("Local" path)
FOLDERID_Public, // PublicShareLocation
FOLDERID_Templates, // TemplatesLocation
+ GUID(), // StateLocation
+ GUID(), // GenericStateLocation
};
- static_assert(sizeof(folderIds) / sizeof(folderIds[0]) == size_t(QStandardPaths::TemplatesLocation + 1));
+ static_assert(sizeof(folderIds) / sizeof(folderIds[0]) == size_t(QStandardPaths::GenericStateLocation + 1));
// folders for low integrity processes
static const GUID folderIds_li[] = {
@@ -130,6 +132,8 @@ static GUID writableSpecialFolderId(QStandardPaths::StandardLocation type)
FOLDERID_LocalAppDataLow,// AppConfigLocation ("Local" path)
FOLDERID_Public, // PublicShareLocation
FOLDERID_Templates, // TemplatesLocation
+ GUID(), // StateLocation
+ GUID(), // GenericStateLocation
};
static_assert(sizeof(folderIds_li) == sizeof(folderIds));
@@ -184,6 +188,23 @@ QString QStandardPaths::writableLocation(StandardLocation type)
result = QDir::tempPath();
break;
+ case StateLocation:
+ result = sHGetKnownFolderPath(writableSpecialFolderId(AppLocalDataLocation));
+ if (!result.isEmpty()) {
+ appendTestMode(result);
+ appendOrganizationAndApp(result);
+ result += "/State"_L1;
+ }
+ break;
+
+ case GenericStateLocation:
+ result = sHGetKnownFolderPath(writableSpecialFolderId(GenericDataLocation));
+ if (!result.isEmpty()) {
+ appendTestMode(result);
+ result += "/State"_L1;
+ }
+ break;
+
default:
result = sHGetKnownFolderPath(writableSpecialFolderId(type));
if (!result.isEmpty() && isConfigLocation(type)) {
diff --git a/src/corelib/io/qstorageinfo.cpp b/src/corelib/io/qstorageinfo.cpp
index 2a12c2cd85..b7a17febaf 100644
--- a/src/corelib/io/qstorageinfo.cpp
+++ b/src/corelib/io/qstorageinfo.cpp
@@ -20,6 +20,8 @@ QT_IMPL_METATYPE_EXTERN(QStorageInfo)
\ingroup io
\ingroup shared
+ \compares equality
+
Allows retrieving information about the volume's space, its mount point,
label, and filesystem name.
@@ -37,6 +39,11 @@ QT_IMPL_METATYPE_EXTERN(QStorageInfo)
\snippet code/src_corelib_io_qstorageinfo.cpp 2
*/
+QStorageInfo::QStorageInfo(QStorageInfoPrivate &dd)
+ : d(&dd)
+{
+}
+
/*!
Constructs an empty QStorageInfo object.
@@ -381,22 +388,29 @@ QStorageInfo QStorageInfo::root()
}
/*!
- \fn bool QStorageInfo::operator==(const QStorageInfo &first, const QStorageInfo &second)
+ \fn bool QStorageInfo::operator==(const QStorageInfo &lhs, const QStorageInfo &rhs)
- Returns true if the \a first QStorageInfo object refers to the same drive or volume
- as the \a second; otherwise it returns false.
+ Returns \c true if the QStorageInfo object \a lhs refers to the same drive or
+ volume as the QStorageInfo object \a rhs; otherwise it returns \c false.
Note that the result of comparing two invalid QStorageInfo objects is always
positive.
*/
/*!
- \fn bool QStorageInfo::operator!=(const QStorageInfo &first, const QStorageInfo &second)
+ \fn bool QStorageInfo::operator!=(const QStorageInfo &lhs, const QStorageInfo &rhs)
- Returns true if the \a first QStorageInfo object refers to a different drive or
- volume than the \a second; otherwise returns false.
+ Returns \c true if the QStorageInfo object \a lhs refers to a different drive or
+ volume than the QStorageInfo object \a rhs; otherwise returns \c false.
*/
+bool comparesEqual(const QStorageInfo &lhs, const QStorageInfo &rhs)
+{
+ if (lhs.d == rhs.d)
+ return true;
+ return lhs.device() == rhs.device() && lhs.rootPath() == rhs.rootPath();
+}
+
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QStorageInfo &s)
{
diff --git a/src/corelib/io/qstorageinfo.h b/src/corelib/io/qstorageinfo.h
index d25eee1840..3784fe8e47 100644
--- a/src/corelib/io/qstorageinfo.h
+++ b/src/corelib/io/qstorageinfo.h
@@ -5,6 +5,7 @@
#define QSTORAGEINFO_H
#include <QtCore/qbytearray.h>
+#include <QtCore/qcompare.h>
#include <QtCore/qdir.h>
#include <QtCore/qlist.h>
#include <QtCore/qmetatype.h>
@@ -56,18 +57,10 @@ public:
static QStorageInfo root();
private:
+ explicit QStorageInfo(QStorageInfoPrivate &dd);
friend class QStorageInfoPrivate;
- friend inline bool operator==(const QStorageInfo &first, const QStorageInfo &second)
- {
- if (first.d == second.d)
- return true;
- return first.device() == second.device() && first.rootPath() == second.rootPath();
- }
-
- friend inline bool operator!=(const QStorageInfo &first, const QStorageInfo &second)
- {
- return !(first == second);
- }
+ friend Q_CORE_EXPORT bool comparesEqual(const QStorageInfo &lhs, const QStorageInfo &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QStorageInfo)
friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QStorageInfo &);
QExplicitlySharedDataPointer<QStorageInfoPrivate> d;
diff --git a/src/corelib/io/qstorageinfo_linux.cpp b/src/corelib/io/qstorageinfo_linux.cpp
new file mode 100644
index 0000000000..b4360f9164
--- /dev/null
+++ b/src/corelib/io/qstorageinfo_linux.cpp
@@ -0,0 +1,348 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2014 Ivan Komissarov <ABBAPOH@gmail.com>
+// Copyright (C) 2016 Intel Corporation.
+// Copyright (C) 2023 Ahmad Samir <a.samirh78@gmail.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qstorageinfo_linux_p.h"
+
+#include "qdirlisting.h"
+#include <private/qcore_unix_p.h>
+#include <private/qtools_p.h>
+
+#include <q20memory.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+
+// so we don't have to #include <linux/fs.h>, which is known to cause conflicts
+#ifndef FSLABEL_MAX
+# define FSLABEL_MAX 256
+#endif
+#ifndef FS_IOC_GETFSLABEL
+# define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX])
+#endif
+
+// or <linux/statfs.h>
+#ifndef ST_RDONLY
+# define ST_RDONLY 0x0001 /* mount read-only */
+#endif
+
+#if defined(Q_OS_ANDROID)
+// statx() is disabled on Android because quite a few systems
+// come with sandboxes that kill applications that make system calls outside a
+// whitelist and several Android vendors can't be bothered to update the list.
+# undef STATX_BASIC_STATS
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace {
+struct AutoFileDescriptor
+{
+ int fd = -1;
+ AutoFileDescriptor(const QString &path, int mode = QT_OPEN_RDONLY)
+ : fd(qt_safe_open(QFile::encodeName(path), mode))
+ {}
+ ~AutoFileDescriptor() { if (fd >= 0) qt_safe_close(fd); }
+ operator int() const noexcept { return fd; }
+};
+}
+
+// udev encodes the labels with ID_LABEL_FS_ENC which is done with
+// blkid_encode_string(). Within this function some 1-byte utf-8
+// characters not considered safe (e.g. '\' or ' ') are encoded as hex
+static QString decodeFsEncString(QString &&str)
+{
+ using namespace QtMiscUtils;
+ qsizetype start = str.indexOf(u'\\');
+ if (start < 0)
+ return std::move(str);
+
+ // decode in-place
+ QString decoded = std::move(str);
+ auto ptr = reinterpret_cast<char16_t *>(decoded.begin());
+ qsizetype in = start;
+ qsizetype out = start;
+ qsizetype size = decoded.size();
+
+ while (in < size) {
+ Q_ASSERT(ptr[in] == u'\\');
+ if (size - in >= 4 && ptr[in + 1] == u'x') { // we need four characters: \xAB
+ int c = fromHex(ptr[in + 2]) << 4;
+ c |= fromHex(ptr[in + 3]);
+ if (Q_UNLIKELY(c < 0))
+ c = QChar::ReplacementCharacter; // bad hex sequence
+ ptr[out++] = c;
+ in += 4;
+ }
+
+ for ( ; in < size; ++in) {
+ char16_t c = ptr[in];
+ if (c == u'\\')
+ break;
+ ptr[out++] = c;
+ }
+ }
+ decoded.resize(out);
+ return decoded;
+}
+
+static inline dev_t deviceIdForPath(const QString &device)
+{
+ QT_STATBUF st;
+ if (QT_STAT(QFile::encodeName(device), &st) < 0)
+ return 0;
+ return st.st_dev;
+}
+
+static inline quint64 mountIdForPath(int fd)
+{
+ if (fd < 0)
+ return 0;
+#if defined(STATX_BASIC_STATS) && defined(STATX_MNT_ID)
+ // STATX_MNT_ID was added in kernel v5.8
+ struct statx st;
+ int r = statx(fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, STATX_MNT_ID, &st);
+ if (r == 0 && (st.stx_mask & STATX_MNT_ID))
+ return st.stx_mnt_id;
+#endif
+ return 0;
+}
+
+static inline quint64 retrieveDeviceId(const QByteArray &device, quint64 deviceId = 0)
+{
+ // major = 0 implies an anonymous block device, so we need to stat() the
+ // actual device to get its dev_t. This is required for btrfs (and possibly
+ // others), which always uses them for all the subvolumes (including the
+ // root):
+ // https://codebrowser.dev/linux/linux/fs/btrfs/disk-io.c.html#btrfs_init_fs_root
+ // https://codebrowser.dev/linux/linux/fs/super.c.html#get_anon_bdev
+ // For everything else, we trust the parameter.
+ if (major(deviceId) != 0)
+ return deviceId;
+
+ // don't even try to stat() a relative path or "/"
+ if (device.size() < 2 || !device.startsWith('/'))
+ return 0;
+
+ QT_STATBUF st;
+ if (QT_STAT(device, &st) < 0)
+ return 0;
+ if (!S_ISBLK(st.st_mode))
+ return 0;
+ return st.st_rdev;
+}
+
+static QDirListing devicesByLabel()
+{
+ static const char pathDiskByLabel[] = "/dev/disk/by-label";
+ static constexpr auto LabelFileFilter =
+ QDir::AllEntries | QDir::System | QDir::Hidden | QDir::NoDotAndDotDot;
+
+ return QDirListing(QLatin1StringView(pathDiskByLabel), LabelFileFilter);
+}
+
+static inline auto retrieveLabels()
+{
+ struct Entry {
+ QString label;
+ quint64 deviceId;
+ };
+ QList<Entry> result;
+
+ for (const auto &dirEntry : devicesByLabel()) {
+ quint64 deviceId = retrieveDeviceId(QFile::encodeName(dirEntry.filePath()));
+ if (!deviceId)
+ continue;
+ result.emplaceBack(Entry{ decodeFsEncString(dirEntry.fileName()), deviceId });
+ }
+ return result;
+}
+
+static std::optional<QString> retrieveLabelViaIoctl(int fd)
+{
+ // FS_IOC_GETFSLABEL was introduced in v4.18; previously it was btrfs-specific.
+ if (fd < 0)
+ return std::nullopt;
+
+ // Note: it doesn't append the null terminator (despite what the man page
+ // says) and the return code on success (0) does not indicate the length.
+ char label[FSLABEL_MAX] = {};
+ int r = ioctl(fd, FS_IOC_GETFSLABEL, &label);
+ if (r < 0)
+ return std::nullopt;
+ return QString::fromUtf8(label);
+}
+
+static inline QString retrieveLabel(const QStorageInfoPrivate &d, int fd, quint64 deviceId)
+{
+ if (auto label = retrieveLabelViaIoctl(fd))
+ return *label;
+
+ deviceId = retrieveDeviceId(d.device, deviceId);
+ if (!deviceId)
+ return QString();
+
+ for (const auto &dirEntry : devicesByLabel()) {
+ if (retrieveDeviceId(QFile::encodeName(dirEntry.filePath())) == deviceId)
+ return decodeFsEncString(dirEntry.fileName());
+ }
+ return QString();
+}
+
+void QStorageInfoPrivate::retrieveVolumeInfo()
+{
+ struct statfs64 statfs_buf;
+ int result;
+ QT_EINTR_LOOP(result, statfs64(QFile::encodeName(rootPath).constData(), &statfs_buf));
+ if (result == 0) {
+ valid = true;
+ ready = true;
+
+ bytesTotal = statfs_buf.f_blocks * statfs_buf.f_frsize;
+ bytesFree = statfs_buf.f_bfree * statfs_buf.f_frsize;
+ bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_frsize;
+ blockSize = int(statfs_buf.f_bsize);
+ readOnly = (statfs_buf.f_flags & ST_RDONLY) != 0;
+ }
+}
+
+static std::vector<MountInfo> parseMountInfo(FilterMountInfo filter = FilterMountInfo::All)
+{
+ QFile file(u"/proc/self/mountinfo"_s);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ return {};
+
+ QByteArray mountinfo = file.readAll();
+ file.close();
+
+ return doParseMountInfo(mountinfo, filter);
+}
+
+void QStorageInfoPrivate::doStat()
+{
+ retrieveVolumeInfo();
+ if (!ready)
+ return;
+
+ rootPath = QFileInfo(rootPath).canonicalFilePath();
+ if (rootPath.isEmpty())
+ return;
+
+ std::vector<MountInfo> infos = parseMountInfo();
+ if (infos.empty()) {
+ rootPath = u'/';
+ return;
+ }
+
+ MountInfo *best = nullptr;
+ AutoFileDescriptor fd(rootPath);
+ if (quint64 mntid = mountIdForPath(fd)) {
+ // We have the mount ID for this path, so find the matching line.
+ auto it = std::find_if(infos.begin(), infos.end(),
+ [mntid](const MountInfo &info) { return info.mntid == mntid; });
+ if (it != infos.end())
+ best = q20::to_address(it);
+ } else {
+ // We have failed to get the mount ID for this path, usually because
+ // the path cannot be open()ed by this user (e.g., /root), so we fall
+ // back to a string search.
+ // We iterate over the /proc/self/mountinfo list backwards because then any
+ // matching isParentOf must be the actual mount point because it's the most
+ // recent mount on that path. Linux does allow mounting over non-empty
+ // directories, such as in:
+ // # mount | tail -2
+ // tmpfs on /tmp/foo/bar type tmpfs (rw,relatime,inode64)
+ // tmpfs on /tmp/foo type tmpfs (rw,relatime,inode64)
+ //
+ // We try to match the device ID in case there's a mount --move.
+ // We can't *rely* on it because some filesystems like btrfs will assign
+ // device IDs to subvolumes that aren't listed in /proc/self/mountinfo.
+
+ const QString oldRootPath = std::exchange(rootPath, QString());
+ const dev_t rootPathDevId = deviceIdForPath(oldRootPath);
+ for (auto it = infos.rbegin(); it != infos.rend(); ++it) {
+ if (!isParentOf(it->mountPoint, oldRootPath))
+ continue;
+ if (rootPathDevId == it->stDev) {
+ // device ID matches; this is definitely the best option
+ best = q20::to_address(it);
+ break;
+ }
+ if (!best) {
+ // if we can't find a device ID match, this parent path is probably
+ // the correct one
+ best = q20::to_address(it);
+ }
+ }
+ }
+ if (best) {
+ auto stDev = best->stDev;
+ setFromMountInfo(std::move(*best));
+ name = retrieveLabel(*this, fd, stDev);
+ }
+}
+
+QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
+{
+ std::vector<MountInfo> infos = parseMountInfo(FilterMountInfo::Filtered);
+ if (infos.empty())
+ return QList{root()};
+
+ std::optional<decltype(retrieveLabels())> labelMap;
+ auto labelForDevice = [&labelMap](const QStorageInfoPrivate &d, int fd, quint64 devid) {
+ if (d.fileSystemType == "tmpfs")
+ return QString();
+
+ if (auto label = retrieveLabelViaIoctl(fd))
+ return *label;
+
+ devid = retrieveDeviceId(d.device, devid);
+ if (!devid)
+ return QString();
+
+ if (!labelMap)
+ labelMap = retrieveLabels();
+ for (auto &[deviceLabel, deviceId] : std::as_const(*labelMap)) {
+ if (devid == deviceId)
+ return deviceLabel;
+ }
+ return QString();
+ };
+
+ QList<QStorageInfo> volumes;
+ volumes.reserve(infos.size());
+ for (auto it = infos.begin(); it != infos.end(); ++it) {
+ MountInfo &info = *it;
+ AutoFileDescriptor fd(info.mountPoint);
+
+ // find out if the path as we see it matches this line from mountinfo
+ quint64 mntid = mountIdForPath(fd);
+ if (mntid == 0) {
+ // statx failed, so scan the later lines to see if any is a parent
+ // to this
+ auto isParent = [&info](const MountInfo &maybeParent) {
+ return isParentOf(maybeParent.mountPoint, info.mountPoint);
+ };
+ if (std::find_if(it + 1, infos.end(), isParent) != infos.end())
+ continue;
+ } else if (mntid != info.mntid) {
+ continue;
+ }
+
+ const auto infoStDev = info.stDev;
+ QStorageInfoPrivate d(std::move(info));
+ d.retrieveVolumeInfo();
+ if (d.bytesTotal <= 0 && d.rootPath != u'/')
+ continue;
+ d.name = labelForDevice(d, fd, infoStDev);
+ volumes.emplace_back(QStorageInfo(*new QStorageInfoPrivate(std::move(d))));
+ }
+ return volumes;
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/io/qstorageinfo_linux_p.h b/src/corelib/io/qstorageinfo_linux_p.h
new file mode 100644
index 0000000000..ae6c1a3859
--- /dev/null
+++ b/src/corelib/io/qstorageinfo_linux_p.h
@@ -0,0 +1,251 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2014 Ivan Komissarov <ABBAPOH@gmail.com>
+// Copyright (C) 2016 Intel Corporation.
+// Copyright (C) 2023 Ahmad Samir <a.samirh78@gmail.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSTORAGEINFO_LINUX_P_H
+#define QSTORAGEINFO_LINUX_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API.
+// This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qstorageinfo_p.h"
+
+#include <QtCore/qsystemdetection.h>
+#include <QtCore/private/qlocale_tools_p.h>
+
+#include <sys/sysmacros.h> // makedev()
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+using MountInfo = QStorageInfoPrivate::MountInfo;
+
+static const char MountInfoPath[] = "/proc/self/mountinfo";
+
+static std::optional<dev_t> deviceNumber(QByteArrayView devno)
+{
+ // major:minor
+ auto it = devno.cbegin();
+ auto r = qstrntoll(it, devno.size(), 10);
+ if (!r.ok())
+ return std::nullopt;
+ int rdevmajor = int(r.result);
+ it += r.used;
+
+ if (*it != ':')
+ return std::nullopt;
+
+ r = qstrntoll(++it, devno.size() - r.used + 1, 10);
+ if (!r.ok())
+ return std::nullopt;
+
+ return makedev(rdevmajor, r.result);
+}
+
+// Helper function to parse paths that the kernel inserts escape sequences
+// for.
+static QByteArray parseMangledPath(QByteArrayView path)
+{
+ // The kernel escapes with octal the following characters:
+ // space ' ', tab '\t', backslash '\\', and newline '\n'
+ // See:
+ // https://codebrowser.dev/linux/linux/fs/proc_namespace.c.html#show_mountinfo
+ // https://codebrowser.dev/linux/linux/fs/seq_file.c.html#mangle_path
+
+ QByteArray ret(path.size(), '\0');
+ char *dst = ret.data();
+ const char *src = path.data();
+ const char *srcEnd = path.data() + path.size();
+ while (src != srcEnd) {
+ switch (*src) {
+ case ' ': // Shouldn't happen
+ return {};
+
+ case '\\': {
+ // It always uses exactly three octal characters.
+ ++src;
+ char c = (*src++ - '0') << 6;
+ c |= (*src++ - '0') << 3;
+ c |= (*src++ - '0');
+ *dst++ = c;
+ break;
+ }
+
+ default:
+ *dst++ = *src++;
+ break;
+ }
+ }
+ // If "path" contains any of the characters this method is demangling,
+ // "ret" would be oversized with extra '\0' characters at the end.
+ ret.resize(dst - ret.data());
+ return ret;
+}
+
+// Indexes into the "fields" std::array in parseMountInfo()
+static constexpr short MountId = 0;
+// static constexpr short ParentId = 1;
+static constexpr short DevNo = 2;
+static constexpr short FsRoot = 3;
+static constexpr short MountPoint = 4;
+static constexpr short MountOptions = 5;
+// static constexpr short OptionalFields = 6;
+// static constexpr short Separator = 7;
+static constexpr short FsType = 8;
+static constexpr short MountSource = 9;
+static constexpr short SuperOptions = 10;
+static constexpr short FieldCount = 11;
+
+// Splits a line from /proc/self/mountinfo into fields; fields are separated
+// by a single space.
+static void tokenizeLine(std::array<QByteArrayView, FieldCount> &fields, QByteArrayView line)
+{
+ size_t fieldIndex = 0;
+ qsizetype from = 0;
+ const char *begin = line.data();
+ const qsizetype len = line.size();
+ qsizetype spaceIndex = -1;
+ while ((spaceIndex = line.indexOf(' ', from)) != -1 && fieldIndex < FieldCount) {
+ fields[fieldIndex] = QByteArrayView{begin + from, begin + spaceIndex};
+ from = spaceIndex;
+
+ // Skip "OptionalFields" and Separator fields
+ if (fieldIndex == MountOptions) {
+ static constexpr char separatorField[] = " - ";
+ const qsizetype sepIndex = line.indexOf(separatorField, from);
+ if (sepIndex == -1) {
+ qCWarning(lcStorageInfo,
+ "Malformed line (missing '-' separator field) while parsing '%s':\n%s",
+ MountInfoPath, line.constData());
+ fields.fill({});
+ return;
+ }
+
+ from = sepIndex + strlen(separatorField);
+ // Continue parsing at FsType field
+ fieldIndex = FsType;
+ continue;
+ }
+
+ if (from + 1 < len)
+ ++from; // Skip the space at spaceIndex
+
+ ++fieldIndex;
+ }
+
+ // Currently we don't use the last field, so just check the index
+ if (fieldIndex != SuperOptions) {
+ qCInfo(lcStorageInfo,
+ "Expected %d fields while parsing line from '%s', but found %zu instead:\n%.*s",
+ FieldCount, MountInfoPath, fieldIndex, int(line.size()), line.data());
+ fields.fill({});
+ }
+}
+
+// parseMountInfo() is called from:
+// - QStorageInfoPrivate::initRootPath(), where a list of all mounted volumes is needed
+// - QStorageInfoPrivate::mountedVolumes(), where some filesystem types are ignored
+// (see shouldIncludefs())
+enum class FilterMountInfo {
+ All,
+ Filtered,
+};
+
+[[maybe_unused]] static std::vector<MountInfo>
+doParseMountInfo(const QByteArray &mountinfo, FilterMountInfo filter = FilterMountInfo::All)
+{
+ // https://www.kernel.org/doc/Documentation/filesystems/proc.txt:
+ // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+ // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
+
+ auto it = mountinfo.cbegin();
+ const auto end = mountinfo.cend();
+ auto nextLine = [&it, &end]() -> QByteArrayView {
+ auto nIt = std::find(it, end, '\n');
+ if (nIt != end) {
+ QByteArrayView ba(it, nIt);
+ it = ++nIt; // Advance
+ return ba;
+ }
+ return {};
+ };
+
+ std::vector<MountInfo> infos;
+ std::array<QByteArrayView, FieldCount> fields;
+ QByteArrayView line;
+
+ auto checkField = [&line](QByteArrayView field) {
+ if (field.isEmpty()) {
+ qDebug("Failed to parse line from %s:\n%.*s", MountInfoPath, int(line.size()),
+ line.data());
+ return false;
+ }
+ return true;
+ };
+
+ // mountinfo has a stable format, no empty lines
+ while (!(line = nextLine()).isEmpty()) {
+ fields.fill({});
+ tokenizeLine(fields, line);
+
+ MountInfo info;
+ if (auto r = qstrntoll(fields[MountId].data(), fields[MountId].size(), 10); r.ok()) {
+ info.mntid = r.result;
+ } else {
+ checkField({});
+ continue;
+ }
+
+ QByteArray mountP = parseMangledPath(fields[MountPoint]);
+ if (!checkField(mountP))
+ continue;
+ info.mountPoint = QFile::decodeName(mountP);
+
+ if (!checkField(fields[FsType]))
+ continue;
+ info.fsType = fields[FsType].toByteArray();
+
+ if (filter == FilterMountInfo::Filtered && !shouldIncludeFs(info.mountPoint, info.fsType))
+ continue;
+
+ std::optional<dev_t> devno = deviceNumber(fields[DevNo]);
+ if (!devno) {
+ checkField({});
+ continue;
+ }
+ info.stDev = *devno;
+
+ QByteArrayView fsRootView = fields[FsRoot];
+ if (!checkField(fsRootView))
+ continue;
+
+ // If the filesystem root is "/" -- it's not a *sub*-volume/bind-mount,
+ // in that case we leave info.fsRoot empty
+ if (fsRootView != "/") {
+ info.fsRoot = parseMangledPath(fsRootView);
+ if (!checkField(info.fsRoot))
+ continue;
+ }
+
+ info.device = parseMangledPath(fields[MountSource]);
+ if (!checkField(info.device))
+ continue;
+
+ infos.push_back(std::move(info));
+ }
+ return infos;
+}
+
+QT_END_NAMESPACE
+
+#endif // QSTORAGEINFO_LINUX_P_H
diff --git a/src/corelib/io/qstorageinfo_mac.cpp b/src/corelib/io/qstorageinfo_mac.cpp
index 690e7212d1..c6c0f501da 100644
--- a/src/corelib/io/qstorageinfo_mac.cpp
+++ b/src/corelib/io/qstorageinfo_mac.cpp
@@ -43,9 +43,9 @@ void QStorageInfoPrivate::retrievePosixInfo()
QT_STATFSBUF statfs_buf;
int result = QT_STATFS(QFile::encodeName(rootPath).constData(), &statfs_buf);
if (result == 0) {
- device = QByteArray(statfs_buf.f_mntfromname);
+ device.assign(statfs_buf.f_mntfromname);
readOnly = (statfs_buf.f_flags & MNT_RDONLY) != 0;
- fileSystemType = QByteArray(statfs_buf.f_fstypename);
+ fileSystemType.assign(statfs_buf.f_fstypename);
blockSize = statfs_buf.f_bsize;
}
}
@@ -160,9 +160,4 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
return volumes;
}
-QStorageInfo QStorageInfoPrivate::root()
-{
- return QStorageInfo(QStringLiteral("/"));
-}
-
QT_END_NAMESPACE
diff --git a/src/corelib/io/qstorageinfo_p.h b/src/corelib/io/qstorageinfo_p.h
index af323b4ddc..3b049cdfd4 100644
--- a/src/corelib/io/qstorageinfo_p.h
+++ b/src/corelib/io/qstorageinfo_p.h
@@ -15,36 +15,78 @@
// We mean it.
//
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qsystemdetection.h>
+#include <QtCore/qtenvironmentvariables.h>
#include <QtCore/private/qglobal_p.h>
#include "qstorageinfo.h"
+#ifdef Q_OS_UNIX
+#include <sys/types.h> // dev_t
+#endif
+
QT_BEGIN_NAMESPACE
+inline Q_LOGGING_CATEGORY(lcStorageInfo, "qt.core.qstorageinfo", QtWarningMsg)
+
class QStorageInfoPrivate : public QSharedData
{
public:
- inline QStorageInfoPrivate() : QSharedData(),
- bytesTotal(-1), bytesFree(-1), bytesAvailable(-1), blockSize(-1),
- readOnly(false), ready(false), valid(false)
- {}
+ QStorageInfoPrivate() = default;
- void initRootPath();
void doStat();
static QList<QStorageInfo> mountedVolumes();
- static QStorageInfo root();
+
+ static QStorageInfo root()
+ {
+#ifdef Q_OS_WIN
+ return QStorageInfo(QDir::fromNativeSeparators(QFile::decodeName(qgetenv("SystemDrive"))));
+#else
+ return QStorageInfo(QStringLiteral("/"));
+#endif
+ };
protected:
#if defined(Q_OS_WIN)
+ void initRootPath();
void retrieveVolumeInfo();
void retrieveDiskFreeSpace();
bool queryStorageProperty();
void queryFileFsSectorSizeInformation();
#elif defined(Q_OS_DARWIN)
+ void initRootPath();
void retrievePosixInfo();
void retrieveUrlProperties(bool initRootPath = false);
void retrieveLabel();
+#elif defined(Q_OS_LINUX)
+ void retrieveVolumeInfo();
+
+public:
+ struct MountInfo {
+ QString mountPoint;
+ QByteArray fsType;
+ QByteArray device;
+ QByteArray fsRoot;
+ dev_t stDev = 0;
+ quint64 mntid = 0;
+ };
+
+ void setFromMountInfo(MountInfo &&info)
+ {
+ rootPath = std::move(info.mountPoint);
+ fileSystemType = std::move(info.fsType);
+ device = std::move(info.device);
+ subvolume = std::move(info.fsRoot);
+ }
+
+ QStorageInfoPrivate(MountInfo &&info)
+ {
+ setFromMountInfo(std::move(info));
+ }
+
#elif defined(Q_OS_UNIX)
+ void initRootPath();
void retrieveVolumeInfo();
#endif
@@ -55,16 +97,68 @@ public:
QByteArray fileSystemType;
QString name;
- qint64 bytesTotal;
- qint64 bytesFree;
- qint64 bytesAvailable;
- ulong blockSize;
+ qint64 bytesTotal = -1;
+ qint64 bytesFree = -1;
+ qint64 bytesAvailable = -1;
+ int blockSize = -1;
- bool readOnly;
- bool ready;
- bool valid;
+ bool readOnly = false;
+ bool ready = false;
+ bool valid = false;
};
+// Common helper functions
+template <typename String>
+static bool isParentOf(const String &parent, const QString &dirName)
+{
+ return dirName.startsWith(parent) &&
+ (dirName.size() == parent.size() || dirName.at(parent.size()) == u'/' ||
+ parent.size() == 1);
+}
+
+static inline bool shouldIncludeFs(const QString &mountDir, const QByteArray &fsType)
+{
+#if defined(Q_OS_ANDROID)
+ // "rootfs" is the filesystem type of "/" on Android
+ static constexpr char RootFsStr[] = "";
+#else
+ // "rootfs" is a type of ramfs on Linux, used in the initrd on some distros
+ static constexpr char RootFsStr[] = "rootfs";
+#endif
+
+ using namespace Qt::StringLiterals;
+ /*
+ * This function implements a heuristic algorithm to determine whether a
+ * given mount should be reported to the user. Our objective is to list
+ * only entries that the end-user would find useful.
+ *
+ * We therefore ignore:
+ * - mounted in /dev, /proc, /sys: special mounts
+ * (this will catch /sys/fs/cgroup, /proc/sys/fs/binfmt_misc, /dev/pts,
+ * some of which are tmpfs on Linux)
+ * - mounted in /var/run or /var/lock: most likely pseudofs
+ * (on earlier systemd versions, /var/run was a bind-mount of /run, so
+ * everything would be unnecessarily duplicated)
+ * - filesystem type is "rootfs": artifact of the root-pivot on some Linux
+ * initrd
+ * - if the filesystem total size is zero, it's a pseudo-fs (not checked here).
+ */
+
+ if (isParentOf("/dev"_L1, mountDir)
+ || isParentOf("/proc"_L1, mountDir)
+ || isParentOf("/sys"_L1, mountDir)
+ || isParentOf("/var/run"_L1, mountDir)
+ || isParentOf("/var/lock"_L1, mountDir)) {
+ return false;
+ }
+
+ if (!fsType.isEmpty() && fsType == RootFsStr)
+ return false;
+
+ // size checking in QStorageInfo::mountedVolumes()
+ return true;
+}
+
QT_END_NAMESPACE
#endif // QSTORAGEINFO_P_H
diff --git a/src/corelib/io/qstorageinfo_stub.cpp b/src/corelib/io/qstorageinfo_stub.cpp
new file mode 100644
index 0000000000..f2f7d2eb65
--- /dev/null
+++ b/src/corelib/io/qstorageinfo_stub.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qstorageinfo_p.h"
+
+QT_BEGIN_NAMESPACE
+
+void QStorageInfoPrivate::initRootPath()
+{
+ Q_UNIMPLEMENTED();
+ rootPath = QString();
+}
+
+void QStorageInfoPrivate::doStat()
+{
+ Q_UNIMPLEMENTED();
+}
+
+QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
+{
+ Q_UNIMPLEMENTED();
+ return QList<QStorageInfo>();
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/io/qstorageinfo_unix.cpp b/src/corelib/io/qstorageinfo_unix.cpp
index c58b7ce87d..9df098a389 100644
--- a/src/corelib/io/qstorageinfo_unix.cpp
+++ b/src/corelib/io/qstorageinfo_unix.cpp
@@ -5,7 +5,6 @@
#include "qstorageinfo_p.h"
-#include <QtCore/qdiriterator.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qtextstream.h>
@@ -18,11 +17,7 @@
#if defined(Q_OS_BSD4)
# include <sys/mount.h>
# include <sys/statvfs.h>
-#elif defined(Q_OS_ANDROID)
-# include <sys/mount.h>
-# include <sys/vfs.h>
-# include <mntent.h>
-#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
+#elif defined(Q_OS_HURD)
# include <mntent.h>
# include <sys/statvfs.h>
# include <sys/sysmacros.h>
@@ -55,12 +50,6 @@
# if !defined(_STATFS_F_FLAGS) && !defined(Q_OS_NETBSD)
# define _STATFS_F_FLAGS 1
# endif
-#elif defined(Q_OS_ANDROID)
-# define QT_STATFS ::statfs
-# define QT_STATFSBUF struct statfs
-# if !defined(ST_RDONLY)
-# define ST_RDONLY 1 // hack for missing define on Android
-# endif
#elif defined(Q_OS_HAIKU)
# define QT_STATFSBUF struct statvfs
# define QT_STATFS ::statvfs
@@ -106,43 +95,10 @@ private:
#elif defined(Q_OS_SOLARIS)
FILE *fp;
mnttab mnt;
-#elif defined(Q_OS_ANDROID)
- QFile file;
- QByteArray m_rootPath;
- QByteArray m_fileSystemType;
- QByteArray m_device;
- QByteArray m_options;
-#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
- struct mountinfoent : public mntent {
- // Details from proc(5) section from /proc/<pid>/mountinfo:
- //(1) mount ID: a unique ID for the mount (may be reused after umount(2)).
- int mount_id;
- //(2) parent ID: the ID of the parent mount (or of self for the top of the mount tree).
-// int parent_id;
- //(3) major:minor: the value of st_dev for files on this filesystem (see stat(2)).
- dev_t rdev;
- //(4) root: the pathname of the directory in the filesystem which forms the root of this mount.
- char *subvolume;
- //(5) mount point: the pathname of the mount point relative to the process's root directory.
-// char *mnt_dir; // in mntent
- //(6) mount options: per-mount options.
-// char *mnt_opts; // in mntent
- //(7) optional fields: zero or more fields of the form "tag[:value]"; see below.
-// int flags;
- //(8) separator: the end of the optional fields is marked by a single hyphen.
-
- //(9) filesystem type: the filesystem type in the form "type[.subtype]".
-// char *mnt_type; // in mntent
- //(10) mount source: filesystem-specific information or "none".
-// char *mnt_fsname; // in mntent
- //(11) super options: per-superblock options.
- char *superopts;
- };
-
+#elif defined(Q_OS_HURD)
FILE *fp;
QByteArray buffer;
mountinfoent mnt;
- bool usingMountinfo;
#elif defined(Q_OS_HAIKU)
BVolumeRoster m_volumeRoster;
@@ -152,51 +108,6 @@ private:
#endif
};
-template <typename String>
-static bool isParentOf(const String &parent, const QString &dirName)
-{
- return dirName.startsWith(parent) &&
- (dirName.size() == parent.size() || dirName.at(parent.size()) == u'/' ||
- parent.size() == 1);
-}
-
-static bool shouldIncludeFs(const QStorageIterator &it)
-{
- /*
- * This function implements a heuristic algorithm to determine whether a
- * given mount should be reported to the user. Our objective is to list
- * only entries that the end-user would find useful.
- *
- * We therefore ignore:
- * - mounted in /dev, /proc, /sys: special mounts
- * (this will catch /sys/fs/cgroup, /proc/sys/fs/binfmt_misc, /dev/pts,
- * some of which are tmpfs on Linux)
- * - mounted in /var/run or /var/lock: most likely pseudofs
- * (on earlier systemd versions, /var/run was a bind-mount of /run, so
- * everything would be unnecessarily duplicated)
- * - filesystem type is "rootfs": artifact of the root-pivot on some Linux
- * initrd
- * - if the filesystem total size is zero, it's a pseudo-fs (not checked here).
- */
-
- QString mountDir = it.rootPath();
- if (isParentOf("/dev"_L1, mountDir)
- || isParentOf("/proc"_L1, mountDir)
- || isParentOf("/sys"_L1, mountDir)
- || isParentOf("/var/run"_L1, mountDir)
- || isParentOf("/var/lock"_L1, mountDir)) {
- return false;
- }
-
-#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
- if (it.fileSystemType() == "rootfs")
- return false;
-#endif
-
- // size checking in mountedVolumes()
- return true;
-}
-
#if defined(Q_OS_BSD4)
#ifndef MNT_NOWAIT
@@ -290,67 +201,8 @@ inline QByteArray QStorageIterator::subvolume() const
{
return QByteArray();
}
-#elif defined(Q_OS_ANDROID)
-inline QStorageIterator::QStorageIterator()
-{
- file.setFileName(QString::fromUtf8(_PATH_MOUNTED));
- file.open(QIODevice::ReadOnly | QIODevice::Text);
-}
-
-inline QStorageIterator::~QStorageIterator()
-{
-}
-
-inline bool QStorageIterator::isValid() const
-{
- return file.isOpen();
-}
-
-inline bool QStorageIterator::next()
-{
- QList<QByteArray> data;
- // If file is virtual, file.readLine() may succeed even when file.atEnd().
- do {
- const QByteArray line = file.readLine();
- if (line.isEmpty() && file.atEnd())
- return false;
- data = line.split(' ');
- } while (data.count() < 4);
-
- m_device = data.at(0);
- m_rootPath = data.at(1);
- m_fileSystemType = data.at(2);
- m_options = data.at(3);
-
- return true;
-}
-
-inline QString QStorageIterator::rootPath() const
-{
- return QFile::decodeName(m_rootPath);
-}
-
-inline QByteArray QStorageIterator::fileSystemType() const
-{
- return m_fileSystemType;
-}
-
-inline QByteArray QStorageIterator::device() const
-{
- return m_device;
-}
-
-inline QByteArray QStorageIterator::options() const
-{
- return m_options;
-}
-
-inline QByteArray QStorageIterator::subvolume() const
-{
- return QByteArray();
-}
-#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
+#elif defined(Q_OS_HURD)
static const int bufferSize = 1024; // 2 paths (mount point+device) and metainfo;
// should be enough
@@ -358,28 +210,13 @@ static const int bufferSize = 1024; // 2 paths (mount point+device) and metainfo
inline QStorageIterator::QStorageIterator() :
buffer(QByteArray(bufferSize, 0))
{
- fp = nullptr;
-
-#ifdef Q_OS_LINUX
- // first, try to open /proc/self/mountinfo, which has more details
- fp = ::fopen("/proc/self/mountinfo", "re");
-#endif
- if (fp) {
- usingMountinfo = true;
- } else {
- usingMountinfo = false;
- fp = ::setmntent(_PATH_MOUNTED, "r");
- }
+ fp = ::setmntent(_PATH_MOUNTED, "r");
}
inline QStorageIterator::~QStorageIterator()
{
- if (fp) {
- if (usingMountinfo)
- ::fclose(fp);
- else
- ::endmntent(fp);
- }
+ if (fp)
+ ::endmntent(fp);
}
inline bool QStorageIterator::isValid() const
@@ -389,143 +226,7 @@ inline bool QStorageIterator::isValid() const
inline bool QStorageIterator::next()
{
- mnt.subvolume = nullptr;
- mnt.superopts = nullptr;
- if (!usingMountinfo)
- return ::getmntent_r(fp, &mnt, buffer.data(), buffer.size()) != nullptr;
-
- // Helper function to parse paths that the kernel inserts escape sequences
- // for. The unescaped string is left at \a src and is properly
- // NUL-terminated. Returns a pointer to the delimiter that terminated the
- // path, or nullptr if it failed.
- auto parseMangledPath = [](char *src) {
- // The kernel escapes with octal the following characters:
- // space ' ', tab '\t', backslask '\\', and newline '\n'
- char *dst = src;
- while (*src) {
- switch (*src) {
- case ' ':
- // Unescaped space: end of the field.
- *dst = '\0';
- return src;
-
- default:
- *dst++ = *src++;
- break;
-
- case '\\':
- // It always uses exactly three octal characters.
- ++src;
- char c = (*src++ - '0') << 6;
- c |= (*src++ - '0') << 3;
- c |= (*src++ - '0');
- *dst++ = c;
- break;
- }
- }
-
- // Found a NUL before the end of the field.
- src = nullptr;
- return src;
- };
-
- char *ptr = buffer.data();
- if (fgets(ptr, buffer.size(), fp) == nullptr)
- return false;
-
- size_t len = strlen(ptr);
- if (len == 0)
- return false;
- while (Q_UNLIKELY(ptr[len - 1] != '\n' && !feof(fp))) {
- // buffer wasn't large enough. Enlarge and try again.
- // (we're readidng from the kernel, so OOM is unlikely)
- buffer.resize((buffer.size() + 4096) & ~4095);
- ptr = buffer.data();
- if (fgets(ptr + len, buffer.size() - len, fp) == nullptr)
- return false;
-
- len += strlen(ptr + len);
- Q_ASSERT(len < size_t(buffer.size()));
- }
- ptr[len - 1] = '\0';
- const char *const stop = ptr + len - 1;
-
- // parse the line
- mnt.mnt_freq = 0;
- mnt.mnt_passno = 0;
-
- auto r = qstrntoll(ptr, stop - ptr, 10);
- if (!r.ok())
- return false;
- mnt.mount_id = r.result;
-
- ptr += r.used;
- r = qstrntoll(ptr, stop - ptr, 10);
- if (!r.ok())
- return false;
- // parent_id = r.result; // member currently not in use
-
- ptr += r.used;
- r = qstrntoll(ptr, stop - ptr, 10);
- if (!r.ok())
- return false;
- ptr += r.used;
- if (*ptr != ':')
- return false;
- int rdevmajor = r.result;
- ++ptr; // Skip over the ':'
- r = qstrntoll(ptr, stop - ptr, 10);
- if (!r.ok())
- return false;
- mnt.rdev = makedev(rdevmajor, r.result);
-
- ptr += r.used;
- if (*ptr != ' ')
- return false;
-
- mnt.subvolume = ++ptr;
- ptr = parseMangledPath(ptr);
- if (!ptr)
- return false;
-
- // unset a subvolume of "/" -- it's not a *sub* volume
- if (mnt.subvolume + 1 == ptr)
- *mnt.subvolume = '\0';
-
- mnt.mnt_dir = ++ptr;
- ptr = parseMangledPath(ptr);
- if (!ptr)
- return false;
-
- mnt.mnt_opts = ++ptr;
- ptr = strchr(ptr, ' ');
- if (!ptr)
- return false;
-
- // we don't parse the flags, so just find the separator
- if (char *const dashed = strstr(ptr, " - ")) {
- *ptr = '\0';
- ptr = dashed + strlen(" - ") - 1;
- } else {
- return false;
- }
-
- mnt.mnt_type = ++ptr;
- ptr = strchr(ptr, ' ');
- if (!ptr)
- return false;
- *ptr = '\0';
-
- mnt.mnt_fsname = ++ptr;
- ptr = parseMangledPath(ptr);
- if (!ptr)
- return false;
-
- mnt.superopts = ++ptr;
- ptr += strcspn(ptr, " \n");
- *ptr = '\0';
-
- return true;
+ return ::getmntent_r(fp, &mnt, buffer.data(), buffer.size()) != nullptr;
}
inline QString QStorageIterator::rootPath() const
@@ -540,49 +241,17 @@ inline QByteArray QStorageIterator::fileSystemType() const
inline QByteArray QStorageIterator::device() const
{
-#ifdef Q_OS_LINUX
- // check that the device exists
- if (mnt.mnt_fsname[0] == '/' && access(mnt.mnt_fsname, F_OK) != 0) {
- // It doesn't, so let's try to resolve the dev_t from /dev/block.
- // Note how strlen("4294967295") == digits10 + 1, so we need to add 1
- // for each number, plus the ':'.
- char buf[sizeof("/dev/block/") + 2 * std::numeric_limits<unsigned>::digits10 + 3];
- QByteArray dev(PATH_MAX, Qt::Uninitialized);
- char *devdata = dev.data();
-
- snprintf(buf, sizeof(buf), "/dev/block/%u:%u", major(mnt.rdev), minor(mnt.rdev));
- if (realpath(buf, devdata)) {
- dev.truncate(strlen(devdata));
- return dev;
- }
- }
-#endif
return QByteArray(mnt.mnt_fsname);
}
inline QByteArray QStorageIterator::options() const
{
- // Merge the two options, starting with the superblock options and letting
- // the per-mount options override.
- const char *superopts = mnt.superopts;
-
- // Both mnt_opts and superopts start with "ro" or "rw", so we can skip the
- // superblock's field (see show_mountinfo() in fs/proc_namespace.c).
- if (superopts && superopts[0] == 'r') {
- if (superopts[2] == '\0') // no other superopts besides "ro" / "rw"?
- superopts = nullptr;
- else if (superopts[2] == ',')
- superopts += 3;
- }
-
- if (superopts)
- return QByteArray(superopts) + ',' + mnt.mnt_opts;
return QByteArray(mnt.mnt_opts);
}
inline QByteArray QStorageIterator::subvolume() const
{
- return QByteArray(mnt.subvolume);
+ return QByteArray();
}
#elif defined(Q_OS_HAIKU)
inline QStorageIterator::QStorageIterator()
@@ -650,6 +319,7 @@ inline QByteArray QStorageIterator::subvolume() const
{
return QByteArray();
}
+
#else
inline QStorageIterator::QStorageIterator()
@@ -696,83 +366,9 @@ inline QByteArray QStorageIterator::subvolume() const
}
#endif
-void QStorageInfoPrivate::initRootPath()
-{
- rootPath = QFileInfo(rootPath).canonicalFilePath();
-
- if (rootPath.isEmpty())
- return;
-
- QStorageIterator it;
- if (!it.isValid()) {
- rootPath = QStringLiteral("/");
- return;
- }
-
- int maxLength = 0;
- const QString oldRootPath = rootPath;
- rootPath.clear();
-
- while (it.next()) {
- const QString mountDir = it.rootPath();
- const QByteArray fsName = it.fileSystemType();
- // we try to find most suitable entry
- if (isParentOf(mountDir, oldRootPath) && maxLength < mountDir.size()) {
- maxLength = mountDir.size();
- rootPath = mountDir;
- device = it.device();
- fileSystemType = fsName;
- subvolume = it.subvolume();
- }
- }
-}
-
-#ifdef Q_OS_LINUX
-// udev encodes the labels with ID_LABEL_FS_ENC which is done with
-// blkid_encode_string(). Within this function some 1-byte utf-8
-// characters not considered safe (e.g. '\' or ' ') are encoded as hex
-static QString decodeFsEncString(const QString &str)
-{
- QString decoded;
- decoded.reserve(str.size());
-
- int i = 0;
- while (i < str.size()) {
- if (i <= str.size() - 4) { // we need at least four characters \xAB
- if (QStringView{str}.sliced(i).startsWith("\\x"_L1)) {
- bool bOk;
- const int code = QStringView{str}.mid(i+2, 2).toInt(&bOk, 16);
- // only decode characters between 0x20 and 0x7f but not
- // the backslash to prevent collisions
- if (bOk && code >= 0x20 && code < 0x80 && code != '\\') {
- decoded += QChar(code);
- i += 4;
- continue;
- }
- }
- }
- decoded += str.at(i);
- ++i;
- }
- return decoded;
-}
-#endif
-
static inline QString retrieveLabel(const QByteArray &device)
{
-#ifdef Q_OS_LINUX
- static const char pathDiskByLabel[] = "/dev/disk/by-label";
-
- QFileInfo devinfo(QFile::decodeName(device));
- QString devicePath = devinfo.canonicalFilePath();
-
- QDirIterator it(QLatin1StringView(pathDiskByLabel), QDir::NoDotAndDotDot);
- while (it.hasNext()) {
- QFileInfo fileInfo = it.nextFileInfo();
- if (fileInfo.isSymLink() && fileInfo.symLinkTarget() == devicePath)
- return decodeFsEncString(fileInfo.fileName());
- }
-#elif defined Q_OS_HAIKU
+#if defined Q_OS_HAIKU
fs_info fsInfo;
memset(&fsInfo, 0, sizeof(fsInfo));
@@ -806,7 +402,7 @@ void QStorageInfoPrivate::retrieveVolumeInfo()
{
QT_STATFSBUF statfs_buf;
int result;
- EINTR_LOOP(result, QT_STATFS(QFile::encodeName(rootPath).constData(), &statfs_buf));
+ QT_EINTR_LOOP(result, QT_STATFS(QFile::encodeName(rootPath).constData(), &statfs_buf));
if (result == 0) {
valid = true;
ready = true;
@@ -820,7 +416,7 @@ void QStorageInfoPrivate::retrieveVolumeInfo()
bytesFree = statfs_buf.f_bfree * statfs_buf.f_frsize;
bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_frsize;
#endif
- blockSize = statfs_buf.f_bsize;
+ blockSize = int(statfs_buf.f_bsize);
#if defined(Q_OS_ANDROID) || defined(Q_OS_BSD4) || defined(Q_OS_INTEGRITY) || defined(Q_OS_RTEMS)
#if defined(_STATFS_F_FLAGS)
readOnly = (statfs_buf.f_flags & ST_RDONLY) != 0;
@@ -831,6 +427,37 @@ void QStorageInfoPrivate::retrieveVolumeInfo()
}
}
+void QStorageInfoPrivate::initRootPath()
+{
+ rootPath = QFileInfo(rootPath).canonicalFilePath();
+
+ if (rootPath.isEmpty())
+ return;
+
+ QStorageIterator it;
+ if (!it.isValid()) {
+ rootPath = QStringLiteral("/");
+ return;
+ }
+
+ int maxLength = 0;
+ const QString oldRootPath = rootPath;
+ rootPath.clear();
+
+ while (it.next()) {
+ const QString mountDir = it.rootPath();
+ const QByteArray fsName = it.fileSystemType();
+ // we try to find most suitable entry
+ if (maxLength < mountDir.size() && isParentOf(mountDir, oldRootPath)) {
+ maxLength = mountDir.size();
+ rootPath = mountDir;
+ device = it.device();
+ fileSystemType = fsName;
+ subvolume = it.subvolume();
+ }
+ }
+}
+
QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
{
QStorageIterator it;
@@ -840,7 +467,7 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
QList<QStorageInfo> volumes;
while (it.next()) {
- if (!shouldIncludeFs(it))
+ if (!shouldIncludeFs(it.rootPath(), it.fileSystemType()))
continue;
const QString mountDir = it.rootPath();
@@ -848,7 +475,7 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
info.d->device = it.device();
info.d->fileSystemType = it.fileSystemType();
info.d->subvolume = it.subvolume();
- if (info.bytesTotal() == 0 && info != root())
+ if (info.bytesTotal() <= 0 && info != root())
continue;
volumes.append(info);
}
@@ -856,9 +483,4 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
return volumes;
}
-QStorageInfo QStorageInfoPrivate::root()
-{
- return QStorageInfo(QStringLiteral("/"));
-}
-
QT_END_NAMESPACE
diff --git a/src/corelib/io/qstorageinfo_win.cpp b/src/corelib/io/qstorageinfo_win.cpp
index 418a8b77f8..3ee1df9f3c 100644
--- a/src/corelib/io/qstorageinfo_win.cpp
+++ b/src/corelib/io/qstorageinfo_win.cpp
@@ -170,11 +170,6 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
return volumes;
}
-QStorageInfo QStorageInfoPrivate::root()
-{
- return QStorageInfo(QDir::fromNativeSeparators(QFile::decodeName(qgetenv("SystemDrive"))));
-}
-
bool QStorageInfoPrivate::queryStorageProperty()
{
QString path = QDir::toNativeSeparators(uR"(\\.\)" + rootPath);
@@ -208,7 +203,7 @@ bool QStorageInfoPrivate::queryStorageProperty()
nullptr);
CloseHandle(handle);
if (result)
- blockSize = saad.BytesPerPhysicalSector;
+ blockSize = int(saad.BytesPerPhysicalSector);
return result;
}
diff --git a/src/corelib/io/qtemporarydir.cpp b/src/corelib/io/qtemporarydir.cpp
index 433ec57383..8187524067 100644
--- a/src/corelib/io/qtemporarydir.cpp
+++ b/src/corelib/io/qtemporarydir.cpp
@@ -4,11 +4,9 @@
#include "qtemporarydir.h"
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
#include "qdebug.h"
-#include "qdiriterator.h"
-#include "qpair.h"
#include "qplatformdefs.h"
#include "qrandom.h"
#include "private/qtemporaryfile_p.h"
@@ -326,4 +324,4 @@ bool QTemporaryDir::remove()
QT_END_NAMESPACE
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
diff --git a/src/corelib/io/qtemporarydir.h b/src/corelib/io/qtemporarydir.h
index 1fdaa83fb0..8737bf9e26 100644
--- a/src/corelib/io/qtemporarydir.h
+++ b/src/corelib/io/qtemporarydir.h
@@ -11,7 +11,7 @@
QT_BEGIN_NAMESPACE
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
class QTemporaryDirPrivate;
@@ -52,7 +52,7 @@ inline void swap(QTemporaryDir &lhs, QTemporaryDir &rhs) noexcept
lhs.swap(rhs);
}
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
QT_END_NAMESPACE
diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp
index 80ea57fb92..4e48a18d91 100644
--- a/src/corelib/io/qtemporaryfile.cpp
+++ b/src/corelib/io/qtemporaryfile.cpp
@@ -70,9 +70,8 @@ QTemporaryFileName::QTemporaryFileName(const QString &templateName)
qfilename.append(".XXXXXX"_L1);
// "Nativify" :-)
- QFileSystemEntry::NativePath filename = QFileSystemEngine::absoluteName(
- QFileSystemEntry(qfilename, QFileSystemEntry::FromInternalPath()))
- .nativeFilePath();
+ QFileSystemEntry::NativePath filename =
+ QFileSystemEntry(QDir::cleanPath(qfilename)).nativeFilePath();
// Find mask in native path
phPos = filename.size();
@@ -157,7 +156,7 @@ QFileSystemEntry::NativePath QTemporaryFileName::generateNext()
return path;
}
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
/*!
\internal
@@ -185,8 +184,9 @@ static bool createFileFromTemplate(NativeFileHandle &file, QTemporaryFileName &t
const DWORD shareMode = (flags & QTemporaryFileEngine::Win32NonShared)
? 0u : (FILE_SHARE_READ | FILE_SHARE_WRITE);
+ const DWORD extraAccessFlags = (flags & QTemporaryFileEngine::Win32NonShared) ? DELETE : 0;
file = CreateFile((const wchar_t *)path.constData(),
- GENERIC_READ | GENERIC_WRITE,
+ GENERIC_READ | GENERIC_WRITE | extraAccessFlags,
shareMode, NULL, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL, NULL);
@@ -391,6 +391,18 @@ bool QTemporaryFileEngine::renameOverwrite(const QString &newName)
QFSFileEngine::close();
return ok;
}
+#ifdef Q_OS_WIN
+ if (flags & Win32NonShared) {
+ QFileSystemEntry newEntry(newName, QFileSystemEntry::FromInternalPath());
+ bool ok = d_func()->nativeRenameOverwrite(newEntry);
+ QFSFileEngine::close();
+ if (ok) {
+ // Match what QFSFileEngine::renameOverwrite() does
+ setFileEntry(std::move(newEntry));
+ }
+ return ok;
+ }
+#endif
QFSFileEngine::close();
return QFSFileEngine::renameOverwrite(newName);
}
@@ -631,6 +643,12 @@ QTemporaryFile::QTemporaryFile()
}
/*!
+ \fn QTemporaryFile::QTemporaryFile(const std::filesystem::path &templateName, QObject *parent)
+ \overload
+ \since 6.7
+*/
+
+/*!
Constructs a QTemporaryFile with a template filename of \a
templateName. Upon opening the temporary file this will be used to create
a unique filename.
@@ -713,7 +731,7 @@ QTemporaryFile::~QTemporaryFile()
return true upon success and will set the fileName() to the unique
filename used.
- \sa fileName()
+ \sa fileName(), QT_USE_NODISCARD_FILE_OPEN
*/
/*!
@@ -792,6 +810,12 @@ QString QTemporaryFile::fileTemplate() const
}
/*!
+ \fn void QTemporaryFile::setFileTemplate(const std::filesystem::path &name)
+ \overload
+ \since 6.7
+*/
+
+/*!
Sets the static portion of the file name to \a name. If the file
template contains XXXXXX that will automatically be replaced with
the unique part of the filename, otherwise a filename will be
@@ -813,6 +837,12 @@ void QTemporaryFile::setFileTemplate(const QString &name)
}
/*!
+ \fn bool QTemporaryFile::rename(const std::filesystem::path &newName)
+ \overload
+ \since 6.7
+*/
+
+/*!
Renames the current temporary file to \a newName and returns true if it
succeeded.
@@ -843,7 +873,6 @@ bool QTemporaryFile::rename(const QString &newName)
if (tef->rename(newName)) {
unsetError();
// engine was able to handle the new name so we just reset it
- tef->setFileName(newName);
d->fileName = newName;
return true;
}
@@ -860,7 +889,11 @@ bool QTemporaryFile::rename(const QString &newName)
Works on the given \a fileName rather than an existing QFile
object.
*/
-
+/*!
+ \fn QTemporaryFile *QTemporaryFile::createNativeFile(const std::filesystem::path &fileName)
+ \overload
+ \since 6.7
+*/
/*!
If \a file is not already a native file, then a QTemporaryFile is created
@@ -948,7 +981,7 @@ bool QTemporaryFile::open(OpenMode flags)
return false;
}
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
QT_END_NAMESPACE
diff --git a/src/corelib/io/qtemporaryfile.h b/src/corelib/io/qtemporaryfile.h
index e1058e566c..9a4476f9d2 100644
--- a/src/corelib/io/qtemporaryfile.h
+++ b/src/corelib/io/qtemporaryfile.h
@@ -14,7 +14,7 @@
QT_BEGIN_NAMESPACE
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
class QTemporaryFilePrivate;
class QLockFilePrivate;
@@ -32,26 +32,59 @@ public:
#ifndef QT_NO_QOBJECT
explicit QTemporaryFile(QObject *parent);
QTemporaryFile(const QString &templateName, QObject *parent);
-#endif
+
+# if QT_CONFIG(cxx17_filesystem) || defined(Q_QDOC)
+ Q_WEAK_OVERLOAD
+ explicit QTemporaryFile(const std::filesystem::path &templateName, QObject *parent = nullptr)
+ : QTemporaryFile(QtPrivate::fromFilesystemPath(templateName), parent)
+ {
+ }
+# endif // QT_CONFIG(cxx17_filesystem)
+#endif // !QT_NO_QOBJECT
+
~QTemporaryFile();
bool autoRemove() const;
void setAutoRemove(bool b);
// ### Hides open(flags)
- bool open() { return open(QIODevice::ReadWrite); }
+ QFILE_MAYBE_NODISCARD bool open() { return open(QIODevice::ReadWrite); }
QString fileName() const override;
QString fileTemplate() const;
void setFileTemplate(const QString &name);
+#if QT_CONFIG(cxx17_filesystem) || defined(Q_QDOC)
+ Q_WEAK_OVERLOAD
+ void setFileTemplate(const std::filesystem::path &name)
+ {
+ return setFileTemplate(QtPrivate::fromFilesystemPath(name));
+ }
+#endif // QT_CONFIG(cxx17_filesystem)
// Hides QFile::rename
bool rename(const QString &newName);
+#if QT_CONFIG(cxx17_filesystem) || defined(Q_QDOC)
+ Q_WEAK_OVERLOAD
+ bool rename(const std::filesystem::path &newName)
+ {
+ return rename(QtPrivate::fromFilesystemPath(newName));
+ }
+#endif // QT_CONFIG(cxx17_filesystem)
+
inline static QTemporaryFile *createNativeFile(const QString &fileName)
{ QFile file(fileName); return createNativeFile(file); }
static QTemporaryFile *createNativeFile(QFile &file);
+#if QT_CONFIG(cxx17_filesystem) || defined(Q_QDOC)
+ Q_WEAK_OVERLOAD
+ static QTemporaryFile *createNativeFile(const std::filesystem::path &fileName)
+ {
+ QFile file(fileName);
+ return createNativeFile(file);
+ }
+#endif // QT_CONFIG(cxx17_filesystem)
+
protected:
bool open(OpenMode flags) override;
@@ -61,7 +94,7 @@ private:
Q_DISABLE_COPY(QTemporaryFile)
};
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
QT_END_NAMESPACE
diff --git a/src/corelib/io/qtemporaryfile_p.h b/src/corelib/io/qtemporaryfile_p.h
index 3a2ed73cf9..d160afe41e 100644
--- a/src/corelib/io/qtemporaryfile_p.h
+++ b/src/corelib/io/qtemporaryfile_p.h
@@ -45,7 +45,7 @@ struct QTemporaryFileName
QFileSystemEntry::NativePath generateNext();
};
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
class QTemporaryFilePrivate : public QFilePrivate
{
@@ -88,8 +88,7 @@ public:
if (filePathIsTemplate) {
d->fileEntry.clear();
} else {
- d->fileEntry = QFileSystemEntry(file);
- QFSFileEngine::setFileName(file);
+ QFSFileEngine::setFileEntry(QFileSystemEntry(file));
}
}
~QTemporaryFileEngine();
@@ -116,7 +115,7 @@ public:
bool unnamedFile = false;
};
-#endif // QT_NO_TEMPORARYFILE
+#endif // QT_CONFIG(temporaryfile)
QT_END_NAMESPACE
diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp
index bb5879c9c4..1e285bb36b 100644
--- a/src/corelib/io/qurl.cpp
+++ b/src/corelib/io/qurl.cpp
@@ -14,6 +14,8 @@
\ingroup network
\ingroup shared
+ \compares weak
+
It can parse and construct URLs in both encoded and unencoded
form. QUrl also has support for internationalized domain names
(IDNs).
@@ -1012,8 +1014,9 @@ inline bool QUrlPrivate::setScheme(const QString &value, qsizetype len, bool doS
inline void QUrlPrivate::setAuthority(const QString &auth, qsizetype from, qsizetype end, QUrl::ParsingMode mode)
{
sectionIsPresent &= ~Authority;
- sectionIsPresent |= Host;
port = -1;
+ if (from == end && !auth.isNull())
+ sectionIsPresent |= Host; // empty but not null authority implies host
// we never actually _loop_
while (from != end) {
@@ -1153,8 +1156,11 @@ inline void QUrlPrivate::setQuery(const QString &value, qsizetype from, qsizetyp
inline void QUrlPrivate::appendHost(QString &appendTo, QUrl::FormattingOptions options) const
{
- if (host.isEmpty())
+ if (host.isEmpty()) {
+ if ((sectionIsPresent & Host) && appendTo.isNull())
+ appendTo.detach();
return;
+ }
if (host.at(0).unicode() == '[') {
// IPv6 addresses might contain a zone-id which needs to be recoded
if (options != 0)
@@ -1272,7 +1278,9 @@ QUrlPrivate::setHost(const QString &value, qsizetype from, qsizetype iend, QUrl:
const qsizetype len = end - begin;
host.clear();
- sectionIsPresent |= Host;
+ sectionIsPresent &= ~Host;
+ if (!value.isNull() || (sectionIsPresent & Authority))
+ sectionIsPresent |= Host;
if (len == 0)
return true;
@@ -2027,11 +2035,6 @@ void QUrl::setAuthority(const QString &authority, ParsingMode mode)
}
d->setAuthority(authority, 0, authority.size(), mode);
- if (authority.isNull()) {
- // QUrlPrivate::setAuthority cleared almost everything
- // but it leaves the Host bit set
- d->sectionIsPresent &= ~QUrlPrivate::Authority;
- }
}
/*!
@@ -2295,8 +2298,7 @@ void QUrl::setHost(const QString &host, ParsingMode mode)
}
if (d->setHost(data, 0, data.size(), mode)) {
- if (host.isNull())
- d->sectionIsPresent &= ~QUrlPrivate::Host;
+ return;
} else if (!data.startsWith(u'[')) {
// setHost failed, it might be IPv6 or IPvFuture in need of bracketing
Q_ASSERT(d->error);
@@ -2309,6 +2311,7 @@ void QUrl::setHost(const QString &host, ParsingMode mode)
// source data contains ':', so it's an IPv6 error
d->error->code = QUrlPrivate::InvalidIPv6AddressError;
}
+ d->sectionIsPresent &= ~QUrlPrivate::Host;
} else {
// succeeded
d->clearError();
@@ -2971,7 +2974,7 @@ QByteArray QUrl::toEncoded(FormattingOptions options) const
Parses \a input and returns the corresponding QUrl. \a input is
assumed to be in encoded form, containing only ASCII characters.
- Parses the URL using \a parsingMode. See setUrl() for more information on
+ Parses the URL using \a mode. See setUrl() for more information on
this parameter. QUrl::DecodedMode is not permitted in this context.
\note In Qt versions prior to 6.7, this function took a QByteArray, not
@@ -3067,88 +3070,101 @@ QByteArray QUrl::toAce(const QString &domain, AceProcessingOptions options)
/*!
\internal
- Returns \c true if this URL is "less than" the given \a url. This
+ \fn bool QUrl::operator<(const QUrl &lhs, const QUrl &rhs)
+
+ Returns \c true if URL \a lhs is "less than" URL \a rhs. This
provides a means of ordering URLs.
*/
-bool QUrl::operator <(const QUrl &url) const
+
+Qt::weak_ordering compareThreeWay(const QUrl &lhs, const QUrl &rhs)
{
- if (!d || !url.d) {
- bool thisIsEmpty = !d || d->isEmpty();
- bool thatIsEmpty = !url.d || url.d->isEmpty();
+ if (!lhs.d || !rhs.d) {
+ bool thisIsEmpty = !lhs.d || lhs.d->isEmpty();
+ bool thatIsEmpty = !rhs.d || rhs.d->isEmpty();
// sort an empty URL first
- return thisIsEmpty && !thatIsEmpty;
+ if (thisIsEmpty) {
+ if (!thatIsEmpty)
+ return Qt::weak_ordering::less;
+ else
+ return Qt::weak_ordering::equivalent;
+ } else {
+ return Qt::weak_ordering::greater;
+ }
}
int cmp;
- cmp = d->scheme.compare(url.d->scheme);
+ cmp = lhs.d->scheme.compare(rhs.d->scheme);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- cmp = d->userName.compare(url.d->userName);
+ cmp = lhs.d->userName.compare(rhs.d->userName);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- cmp = d->password.compare(url.d->password);
+ cmp = lhs.d->password.compare(rhs.d->password);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- cmp = d->host.compare(url.d->host);
+ cmp = lhs.d->host.compare(rhs.d->host);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- if (d->port != url.d->port)
- return d->port < url.d->port;
+ if (lhs.d->port != rhs.d->port)
+ return Qt::compareThreeWay(lhs.d->port, rhs.d->port);
- cmp = d->path.compare(url.d->path);
+ cmp = lhs.d->path.compare(rhs.d->path);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- if (d->hasQuery() != url.d->hasQuery())
- return url.d->hasQuery();
+ if (lhs.d->hasQuery() != rhs.d->hasQuery())
+ return rhs.d->hasQuery() ? Qt::weak_ordering::less : Qt::weak_ordering::greater;
- cmp = d->query.compare(url.d->query);
+ cmp = lhs.d->query.compare(rhs.d->query);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- if (d->hasFragment() != url.d->hasFragment())
- return url.d->hasFragment();
+ if (lhs.d->hasFragment() != rhs.d->hasFragment())
+ return rhs.d->hasFragment() ? Qt::weak_ordering::less : Qt::weak_ordering::greater;
- cmp = d->fragment.compare(url.d->fragment);
- return cmp < 0;
+ cmp = lhs.d->fragment.compare(rhs.d->fragment);
+ return Qt::compareThreeWay(cmp, 0);
}
/*!
- Returns \c true if this URL and the given \a url are equal;
+ \fn bool QUrl::operator==(const QUrl &lhs, const QUrl &rhs)
+
+ Returns \c true if \a lhs and \a rhs URLs are equivalent;
otherwise returns \c false.
\sa matches()
*/
-bool QUrl::operator ==(const QUrl &url) const
+
+bool comparesEqual(const QUrl &lhs, const QUrl &rhs)
{
- if (!d && !url.d)
+ if (!lhs.d && !rhs.d)
return true;
- if (!d)
- return url.d->isEmpty();
- if (!url.d)
- return d->isEmpty();
+ if (!lhs.d)
+ return rhs.d->isEmpty();
+ if (!rhs.d)
+ return lhs.d->isEmpty();
// First, compare which sections are present, since it speeds up the
// processing considerably. We just have to ignore the host-is-present flag
// for local files (the "file" protocol), due to the requirements of the
// XDG file URI specification.
int mask = QUrlPrivate::FullUrl;
- if (isLocalFile())
+ if (lhs.isLocalFile())
mask &= ~QUrlPrivate::Host;
- return (d->sectionIsPresent & mask) == (url.d->sectionIsPresent & mask) &&
- d->scheme == url.d->scheme &&
- d->userName == url.d->userName &&
- d->password == url.d->password &&
- d->host == url.d->host &&
- d->port == url.d->port &&
- d->path == url.d->path &&
- d->query == url.d->query &&
- d->fragment == url.d->fragment;
+ return (lhs.d->sectionIsPresent & mask) == (rhs.d->sectionIsPresent & mask) &&
+ lhs.d->scheme == rhs.d->scheme &&
+ lhs.d->userName == rhs.d->userName &&
+ lhs.d->password == rhs.d->password &&
+ lhs.d->host == rhs.d->host &&
+ lhs.d->port == rhs.d->port &&
+ lhs.d->path == rhs.d->path &&
+ lhs.d->query == rhs.d->query &&
+ lhs.d->fragment == rhs.d->fragment;
}
/*!
@@ -3228,15 +3244,13 @@ bool QUrl::matches(const QUrl &url, FormattingOptions options) const
}
/*!
- Returns \c true if this URL and the given \a url are not equal;
+ \fn bool QUrl::operator !=(const QUrl &lhs, const QUrl &rhs)
+
+ Returns \c true if \a lhs and \a rhs URLs are not equal;
otherwise returns \c false.
\sa matches()
*/
-bool QUrl::operator !=(const QUrl &url) const
-{
- return !(*this == url);
-}
/*!
Assigns the specified \a url to this object.
diff --git a/src/corelib/io/qurl.h b/src/corelib/io/qurl.h
index b84348b650..d6779cf485 100644
--- a/src/corelib/io/qurl.h
+++ b/src/corelib/io/qurl.h
@@ -6,10 +6,10 @@
#define QURL_H
#include <QtCore/qbytearray.h>
+#include <QtCore/qcompare.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qstring.h>
#include <QtCore/qlist.h>
-#include <QtCore/qpair.h>
#include <QtCore/qglobal.h>
#if defined(Q_OS_DARWIN) || defined(Q_QDOC)
@@ -231,9 +231,11 @@ public:
void detach();
bool isDetached() const;
+#if QT_CORE_REMOVED_SINCE(6, 8)
bool operator <(const QUrl &url) const;
bool operator ==(const QUrl &url) const;
bool operator !=(const QUrl &url) const;
+#endif
bool matches(const QUrl &url, FormattingOptions options) const;
@@ -269,6 +271,11 @@ public:
friend Q_CORE_EXPORT size_t qHash(const QUrl &url, size_t seed) noexcept;
private:
+ friend Q_CORE_EXPORT bool comparesEqual(const QUrl &lhs, const QUrl &rhs);
+ friend Q_CORE_EXPORT Qt::weak_ordering
+ compareThreeWay(const QUrl &lhs, const QUrl &rhs);
+ Q_DECLARE_WEAKLY_ORDERED(QUrl)
+
QUrlPrivate *d;
friend class QUrlQuery;
diff --git a/src/corelib/io/qurlidna.cpp b/src/corelib/io/qurlidna.cpp
index da592eb81b..a2a81c7605 100644
--- a/src/corelib/io/qurlidna.cpp
+++ b/src/corelib/io/qurlidna.cpp
@@ -132,7 +132,7 @@ Q_AUTOTEST_EXPORT void qt_punycodeEncoder(QStringView in, QString *output)
// delta = delta + (m - n) * (h + 1), fail on overflow
uint tmp;
- if (mul_overflow<uint>(m - n, h + 1, &tmp) || add_overflow<uint>(delta, tmp, &delta)) {
+ if (qMulOverflow<uint>(m - n, h + 1, &tmp) || qAddOverflow<uint>(delta, tmp, &delta)) {
output->truncate(outLen);
return; // punycode_overflow
}
@@ -144,7 +144,7 @@ Q_AUTOTEST_EXPORT void qt_punycodeEncoder(QStringView in, QString *output)
// increase delta until we reach the character processed in this iteration;
// fail if delta overflows.
if (c < n) {
- if (add_overflow<uint>(delta, 1, &delta)) {
+ if (qAddOverflow<uint>(delta, 1, &delta)) {
output->truncate(outLen);
return; // punycode_overflow
}
@@ -219,7 +219,7 @@ Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc)
// i = i + digit * w, fail on overflow
uint tmp;
- if (mul_overflow<uint>(digit, w, &tmp) || add_overflow<uint>(i, tmp, &i))
+ if (qMulOverflow<uint>(digit, w, &tmp) || qAddOverflow<uint>(i, tmp, &i))
return QString();
// detect threshold to stop reading delta digits
@@ -231,7 +231,7 @@ Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc)
if (digit < t) break;
// w = w * (base - t), fail on overflow
- if (mul_overflow<uint>(w, base - t, &w))
+ if (qMulOverflow<uint>(w, base - t, &w))
return QString();
}
@@ -241,7 +241,7 @@ Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc)
bias = adapt(i - oldi, outputLength + 1, oldi == 0);
// n = n + i div (length(output) + 1), fail on overflow
- if (add_overflow<uint>(n, i / (outputLength + 1), &n))
+ if (qAddOverflow<uint>(n, i / (outputLength + 1), &n))
return QString();
// allow the deltas to wrap around
@@ -423,13 +423,19 @@ static QString mapDomainName(const QString &in, QUrl::AceProcessingOptions optio
if (uc >= U'A' && uc <= U'Z')
uc |= 0x20; // lower-case it
- if (!isValidInNormalizedAsciiName(uc))
- return {};
+ if (isValidInNormalizedAsciiName(uc)) {
+ result.append(static_cast<char16_t>(uc));
+ continue;
+ }
+ }
+
+ allAscii = false;
- result.append(static_cast<char16_t>(uc));
+ // Capital sharp S is a special case since UTR #46 revision 31 (Unicode 15.1)
+ if (uc == 0x1E9E && options.testFlag(QUrl::AceTransitionalProcessing)) {
+ result.append(u"ss"_s);
continue;
}
- allAscii = false;
QUnicodeTables::IdnaStatus status = QUnicodeTables::idnaStatus(uc);
@@ -442,14 +448,13 @@ static QString mapDomainName(const QString &in, QUrl::AceProcessingOptions optio
case QUnicodeTables::IdnaStatus::Ignored:
continue;
case QUnicodeTables::IdnaStatus::Valid:
+ case QUnicodeTables::IdnaStatus::Disallowed:
for (auto c : QChar::fromUcs4(uc))
result.append(c);
break;
case QUnicodeTables::IdnaStatus::Mapped:
result.append(QUnicodeTables::idnaMapping(uc));
break;
- case QUnicodeTables::IdnaStatus::Disallowed:
- return {};
default:
Q_UNREACHABLE();
}
@@ -483,12 +488,13 @@ class DomainValidityChecker
{
bool domainNameIsBidi = false;
bool hadBidiErrors = false;
+ bool ignoreBidiErrors;
static constexpr char32_t ZWNJ = U'\u200C';
static constexpr char32_t ZWJ = U'\u200D';
public:
- DomainValidityChecker() { }
+ DomainValidityChecker(bool ignoreBidiErrors = false) : ignoreBidiErrors(ignoreBidiErrors) { }
bool checkLabel(const QString &label, QUrl::AceProcessingOptions options);
private:
@@ -714,7 +720,7 @@ bool DomainValidityChecker::checkLabel(const QString &label, QUrl::AceProcessing
// because non-BMP characters are unlikely to be used for specifying
// future extensions.
if (label[2] == u'-' && label[3] == u'-')
- return false;
+ return ignoreBidiErrors && label.startsWith(u"xn") && validateAsciiLabel(label);
}
if (label.startsWith(u'-') || label.endsWith(u'-'))
@@ -736,7 +742,7 @@ bool DomainValidityChecker::checkLabel(const QString &label, QUrl::AceProcessing
for (;;) {
hasJoiners = hasJoiners || c == ZWNJ || c == ZWJ;
- if (!domainNameIsBidi) {
+ if (!ignoreBidiErrors && !domainNameIsBidi) {
switch (QChar::direction(c)) {
case QChar::DirR:
case QChar::DirAL:
@@ -777,25 +783,20 @@ bool DomainValidityChecker::checkLabel(const QString &label, QUrl::AceProcessing
return true;
}
-static QString convertToAscii(const QString &normalizedDomain, AceLeadingDot dot)
+static QString convertToAscii(QStringView normalizedDomain, AceLeadingDot dot)
{
qsizetype lastIdx = 0;
QString aceForm; // this variable is here for caching
QString aceResult;
while (true) {
- auto idx = normalizedDomain.indexOf(u'.', lastIdx);
+ qsizetype idx = normalizedDomain.indexOf(u'.', lastIdx);
if (idx == -1)
idx = normalizedDomain.size();
- const auto labelLength = idx - lastIdx;
- if (labelLength == 0) {
- if (idx == normalizedDomain.size())
- break;
- if (dot == ForbidLeadingDot || idx > 0)
- return {}; // two delimiters in a row -- empty label not allowed
- } else {
- const auto label = QStringView(normalizedDomain).sliced(lastIdx, labelLength);
+ const qsizetype labelLength = idx - lastIdx;
+ if (labelLength) {
+ const auto label = normalizedDomain.sliced(lastIdx, labelLength);
aceForm.clear();
qt_punycodeEncoder(label, &aceForm);
if (aceForm.isEmpty())
@@ -807,6 +808,9 @@ static QString convertToAscii(const QString &normalizedDomain, AceLeadingDot dot
if (idx == normalizedDomain.size())
break;
+ if (labelLength == 0 && (dot == ForbidLeadingDot || idx > 0))
+ return {}; // two delimiters in a row -- empty label not allowed
+
lastIdx = idx + 1;
aceResult += u'.';
}
@@ -814,7 +818,7 @@ static QString convertToAscii(const QString &normalizedDomain, AceLeadingDot dot
return aceResult;
}
-static bool checkAsciiDomainName(const QString &normalizedDomain, AceLeadingDot dot,
+static bool checkAsciiDomainName(QStringView normalizedDomain, AceLeadingDot dot,
bool *usesPunycode)
{
qsizetype lastIdx = 0;
@@ -833,7 +837,7 @@ static bool checkAsciiDomainName(const QString &normalizedDomain, AceLeadingDot
if (dot == ForbidLeadingDot || idx > 0)
return false; // two delimiters in a row -- empty label not allowed
} else {
- const auto label = QStringView(normalizedDomain).sliced(lastIdx, labelLength);
+ const auto label = normalizedDomain.sliced(lastIdx, labelLength);
if (!validateAsciiLabel(label))
return false;
@@ -886,6 +890,33 @@ static QString convertToUnicode(const QString &asciiDomain, QUrl::AceProcessingO
return result;
}
+static bool checkUnicodeName(const QString &domainName, QUrl::AceProcessingOptions options)
+{
+ qsizetype lastIdx = 0;
+
+ DomainValidityChecker checker(true);
+
+ while (true) {
+ qsizetype idx = domainName.indexOf(u'.', lastIdx);
+ if (idx == -1)
+ idx = domainName.size();
+
+ const qsizetype labelLength = idx - lastIdx;
+ if (labelLength) {
+ const auto label = domainName.sliced(lastIdx, labelLength);
+
+ if (!checker.checkLabel(label, options))
+ return false;
+ }
+
+ if (idx == domainName.size())
+ break;
+
+ lastIdx = idx + 1;
+ }
+ return true;
+}
+
QString qt_ACE_do(const QString &domain, AceOperation op, AceLeadingDot dot,
QUrl::AceProcessingOptions options)
{
@@ -900,12 +931,15 @@ QString qt_ACE_do(const QString &domain, AceOperation op, AceLeadingDot dot,
if (normalized.isEmpty())
return {};
- bool needsCoversionToUnicode;
+ if (!mappedToAscii && !checkUnicodeName(normalized, options))
+ return {};
+
+ bool needsConversionToUnicode;
const QString aceResult = mappedToAscii ? normalized : convertToAscii(normalized, dot);
- if (aceResult.isEmpty() || !checkAsciiDomainName(aceResult, dot, &needsCoversionToUnicode))
+ if (aceResult.isEmpty() || !checkAsciiDomainName(aceResult, dot, &needsConversionToUnicode))
return {};
- if (op == ToAceOnly || !needsCoversionToUnicode
+ if (op == ToAceOnly || !needsConversionToUnicode
|| (!options.testFlag(QUrl::IgnoreIDNWhitelist) && !qt_is_idn_enabled(aceResult))) {
return aceResult;
}
diff --git a/src/corelib/io/qurlquery.cpp b/src/corelib/io/qurlquery.cpp
index c707e8a2e6..31f3ee1d90 100644
--- a/src/corelib/io/qurlquery.cpp
+++ b/src/corelib/io/qurlquery.cpp
@@ -24,6 +24,8 @@ QT_BEGIN_NAMESPACE
\ingroup network
\ingroup shared
+ \compares equality
+
It is used to parse the query strings found in URLs like the following:
\image qurl-querystring.png
@@ -123,14 +125,14 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn QUrlQuery::QUrlQuery(std::initializer_list<QPair<QString, QString>> list)
+ \fn QUrlQuery::QUrlQuery(std::initializer_list<std::pair<QString, QString>> list)
\since 5.13
Constructs a QUrlQuery object from the \a list of key/value pair.
*/
-typedef QList<QPair<QString, QString> > Map;
+typedef QList<std::pair<QString, QString> > Map;
class QUrlQueryPrivate : public QSharedData
{
@@ -146,7 +148,7 @@ public:
void setQuery(const QString &query);
void addQueryItem(const QString &key, const QString &value)
- { itemList.append(qMakePair(recodeFromUser(key), recodeFromUser(value))); }
+ { itemList.append(std::make_pair(recodeFromUser(key), recodeFromUser(value))); }
int findRecodedKey(const QString &key, int from = 0) const
{
for (int i = from; i < itemList.size(); ++i)
@@ -290,17 +292,17 @@ void QUrlQueryPrivate::setQuery(const QString &query)
if (delimiter == pos) {
// the value delimiter wasn't found, store a null value
- itemList.append(qMakePair(key, QString()));
+ itemList.append(std::make_pair(key, QString()));
} else if (delimiter + 1 == pos) {
// if the delimiter was found but the value is empty, store empty-but-not-null
- itemList.append(qMakePair(key, QString(0, Qt::Uninitialized)));
+ itemList.append(std::make_pair(key, QString(0, Qt::Uninitialized)));
} else {
QString value;
if (!qt_urlRecode(value, QStringView{delimiter + 1, pos},
QUrl::DecodeReserved,
prettyDecodedActions))
value = QString(delimiter + 1, pos - delimiter - 1);
- itemList.append(qMakePair(key, value));
+ itemList.append(std::make_pair(key, value));
}
if (pos != end)
@@ -399,22 +401,25 @@ QUrlQuery::~QUrlQuery()
}
/*!
- Returns \c true if this object and the \a other object contain the same
+ \fn bool QUrlQuery::operator==(const QUrlQuery &lhs, const QUrlQuery &rhs)
+
+ Returns \c true if QUrlQuery objects \a lhs and \a rhs contain the same
contents, in the same order, and use the same query delimiters.
*/
-bool QUrlQuery::operator ==(const QUrlQuery &other) const
+
+bool comparesEqual(const QUrlQuery &lhs, const QUrlQuery &rhs)
{
- if (d == other.d)
+ if (lhs.d == rhs.d)
return true;
- if (d && other.d)
+ if (lhs.d && rhs.d)
// keep in sync with qHash(QUrlQuery):
- return d->valueDelimiter == other.d->valueDelimiter &&
- d->pairDelimiter == other.d->pairDelimiter &&
- d->itemList == other.d->itemList;
+ return lhs.d->valueDelimiter == rhs.d->valueDelimiter &&
+ lhs.d->pairDelimiter == rhs.d->pairDelimiter &&
+ lhs.d->itemList == rhs.d->itemList;
- const QUrlQueryPrivate *x = d ? d.data() : other.d.data();
- return x->valueDelimiter == defaultQueryValueDelimiter() &&
- x->pairDelimiter == defaultQueryPairDelimiter() &&
+ const QUrlQueryPrivate *x = lhs.d ? lhs.d.data() : rhs.d.data();
+ return x->valueDelimiter == QUrlQuery::defaultQueryValueDelimiter() &&
+ x->pairDelimiter == QUrlQuery::defaultQueryPairDelimiter() &&
x->itemList.isEmpty();
}
@@ -558,7 +563,7 @@ QString QUrlQuery::query(QUrl::ComponentFormattingOptions encoding) const
representation of the keys and values of the query string are
percent encoded when returned in query().
- If \a valueDelimiter is set to '(' and \a pairDelimiter is ')',
+ If \a valueDelimiter is set to ',' and \a pairDelimiter is ';',
the above query string would instead be represented like this:
\snippet code/src_corelib_io_qurl.cpp 4
@@ -569,7 +574,7 @@ QString QUrlQuery::query(QUrl::ComponentFormattingOptions encoding) const
\snippet code/src_corelib_io_qurlquery.cpp 0
Use of other characters is not supported and may result in unexpected
- behaviour. This method does not verify that you passed a valid delimiter.
+ behavior. This method does not verify that you passed a valid delimiter.
\sa queryValueDelimiter(), queryPairDelimiter()
*/
@@ -613,14 +618,14 @@ QChar QUrlQuery::queryPairDelimiter() const
\sa queryItems(), isEmpty()
*/
-void QUrlQuery::setQueryItems(const QList<QPair<QString, QString> > &query)
+void QUrlQuery::setQueryItems(const QList<std::pair<QString, QString> > &query)
{
clear();
if (query.isEmpty())
return;
QUrlQueryPrivate *dd = d;
- QList<QPair<QString, QString> >::const_iterator it = query.constBegin(),
+ QList<std::pair<QString, QString> >::const_iterator it = query.constBegin(),
end = query.constEnd();
for ( ; it != end; ++it)
dd->addQueryItem(it->first, it->second);
@@ -634,20 +639,20 @@ void QUrlQuery::setQueryItems(const QList<QPair<QString, QString> > &query)
\sa setQueryItems(), {encoding}{Encoding}
*/
-QList<QPair<QString, QString> > QUrlQuery::queryItems(QUrl::ComponentFormattingOptions encoding) const
+QList<std::pair<QString, QString> > QUrlQuery::queryItems(QUrl::ComponentFormattingOptions encoding) const
{
if (!d)
- return QList<QPair<QString, QString> >();
+ return QList<std::pair<QString, QString> >();
if (idempotentRecodeToUser(encoding))
return d->itemList;
- QList<QPair<QString, QString> > result;
+ QList<std::pair<QString, QString> > result;
Map::const_iterator it = d->itemList.constBegin();
Map::const_iterator end = d->itemList.constEnd();
result.reserve(d->itemList.size());
for ( ; it != end; ++it)
- result << qMakePair(d->recodeToUser(it->first, encoding),
- d->recodeToUser(it->second, encoding));
+ result << std::make_pair(d->recodeToUser(it->first, encoding),
+ d->recodeToUser(it->second, encoding));
return result;
}
@@ -766,7 +771,7 @@ void QUrlQuery::removeAllQueryItems(const QString &key)
if (d.constData()) {
auto *p = d.data();
const QString encodedKey = p->recodeFromUser(key);
- auto firstEqualsEncodedKey = [&encodedKey](const QPair<QString, QString> &item) {
+ auto firstEqualsEncodedKey = [&encodedKey](const std::pair<QString, QString> &item) {
return item.first == encodedKey;
};
p->itemList.removeIf(firstEqualsEncodedKey);
@@ -810,9 +815,10 @@ void QUrlQuery::removeAllQueryItems(const QString &key)
*/
/*!
- \fn bool QUrlQuery::operator!=(const QUrlQuery &other) const
+ \fn bool QUrlQuery::operator!=(const QUrlQuery &lhs, const QUrlQuery &rhs)
- Returns \c true if \a other is not equal to this QUrlQuery. Otherwise, returns \c false.
+ Returns \c true if the QUrlQuery object \a rhs is not equal to \a lhs.
+ Otherwise, returns \c false.
\sa operator==()
*/
diff --git a/src/corelib/io/qurlquery.h b/src/corelib/io/qurlquery.h
index 411f19a4c8..061107606e 100644
--- a/src/corelib/io/qurlquery.h
+++ b/src/corelib/io/qurlquery.h
@@ -5,7 +5,7 @@
#ifndef QURLQUERY_H
#define QURLQUERY_H
-#include <QtCore/qpair.h>
+#include <QtCore/qcompare.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qurl.h>
@@ -22,10 +22,10 @@ public:
QUrlQuery();
explicit QUrlQuery(const QUrl &url);
explicit QUrlQuery(const QString &queryString);
- QUrlQuery(std::initializer_list<QPair<QString, QString>> list)
+ QUrlQuery(std::initializer_list<std::pair<QString, QString>> list)
: QUrlQuery()
{
- for (const QPair<QString, QString> &item : list)
+ for (const std::pair<QString, QString> &item : list)
addQueryItem(item.first, item.second);
}
@@ -35,9 +35,11 @@ public:
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QUrlQuery)
~QUrlQuery();
+#if QT_CORE_REMOVED_SINCE(6, 8)
bool operator==(const QUrlQuery &other) const;
bool operator!=(const QUrlQuery &other) const
- { return !(*this == other); }
+ { return !operator==(other); }
+#endif
void swap(QUrlQuery &other) noexcept { d.swap(other.d); }
@@ -54,8 +56,8 @@ public:
QChar queryValueDelimiter() const;
QChar queryPairDelimiter() const;
- void setQueryItems(const QList<QPair<QString, QString> > &query);
- QList<QPair<QString, QString> > queryItems(QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const;
+ void setQueryItems(const QList<std::pair<QString, QString> > &query);
+ QList<std::pair<QString, QString> > queryItems(QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const;
bool hasQueryItem(const QString &key) const;
void addQueryItem(const QString &key, const QString &value);
@@ -68,6 +70,8 @@ public:
static constexpr char16_t defaultQueryPairDelimiter() noexcept { return u'&'; }
private:
+ friend Q_CORE_EXPORT bool comparesEqual(const QUrlQuery &lhs, const QUrlQuery &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QUrlQuery)
friend class QUrl;
friend Q_CORE_EXPORT size_t qHash(const QUrlQuery &key, size_t seed) noexcept;
QSharedDataPointer<QUrlQueryPrivate> d;
diff --git a/src/corelib/io/qwindowspipereader.cpp b/src/corelib/io/qwindowspipereader.cpp
index e0ae9607c8..31d0dc1417 100644
--- a/src/corelib/io/qwindowspipereader.cpp
+++ b/src/corelib/io/qwindowspipereader.cpp
@@ -511,3 +511,5 @@ bool QWindowsPipeReader::waitForNotification()
}
QT_END_NAMESPACE
+
+#include "moc_qwindowspipereader_p.cpp"
diff --git a/src/corelib/io/qwindowspipewriter.cpp b/src/corelib/io/qwindowspipewriter.cpp
index 24f8981042..9d0f6a8a3e 100644
--- a/src/corelib/io/qwindowspipewriter.cpp
+++ b/src/corelib/io/qwindowspipewriter.cpp
@@ -314,3 +314,5 @@ bool QWindowsPipeWriter::consumePendingAndEmit(bool allowWinActPosting)
}
QT_END_NAMESPACE
+
+#include "moc_qwindowspipewriter_p.cpp"
diff --git a/src/corelib/io/qzip.cpp b/src/corelib/io/qzip.cpp
index d4976691c9..173563ec29 100644
--- a/src/corelib/io/qzip.cpp
+++ b/src/corelib/io/qzip.cpp
@@ -730,37 +730,37 @@ void QZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const
*/
/*!
- \variable FileInfo::filePath
+ \variable QZipReader::FileInfo::filePath
The full filepath inside the archive.
*/
/*!
- \variable FileInfo::isDir
+ \variable QZipReader::FileInfo::isDir
A boolean type indicating if the entry is a directory.
*/
/*!
- \variable FileInfo::isFile
+ \variable QZipReader::FileInfo::isFile
A boolean type, if it is one this entry is a file.
*/
/*!
- \variable FileInfo::isSymLink
+ \variable QZipReader::FileInfo::isSymLink
A boolean type, if it is one this entry is symbolic link.
*/
/*!
- \variable FileInfo::permissions
+ \variable QZipReader::FileInfo::permissions
A list of flags for the permissions of this entry.
*/
/*!
- \variable FileInfo::crc
+ \variable QZipReader::FileInfo::crc
The calculated checksum as a crc type.
*/
/*!
- \variable FileInfo::size
+ \variable QZipReader::FileInfo::size
The total size of the unpacked content.
*/