qtbase/src/corelib/io/qresource.cpp
Tor Arne Vestbø 201045a1fd Remove remnants of Qt for Native Client (NACL)
The project has been superseded by Qt for WebAssembly and was
never supported in Qt 6.

Pick-to: 6.6 6.5
Change-Id: I36682cfe3ce6adac76a307b0faba97dcb7c655cc
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-23 13:40:44 +02:00

1600 lines
47 KiB
C++

// Copyright (C) 2019 The Qt Company Ltd.
// Copyright (C) 2020 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qresource.h"
#include "qresource_p.h"
#include "qresource_iterator_p.h"
#include "qset.h"
#include <private/qlocking_p.h>
#include "qdebug.h"
#include "qlocale.h"
#include "qglobal.h"
#include "qlist.h"
#include "qdatetime.h"
#include "qbytearray.h"
#include "qstringlist.h"
#include "qendian.h"
#include <qshareddata.h>
#include <qplatformdefs.h>
#include <qendian.h>
#include "private/qabstractfileengine_p.h"
#include "private/qduplicatetracker_p.h"
#include "private/qnumeric_p.h"
#include "private/qsimd_p.h"
#include "private/qtools_p.h"
#include "private/qsystemerror_p.h"
#ifndef QT_NO_COMPRESS
# include <zconf.h>
# include <zlib.h>
#endif
#if QT_CONFIG(zstd)
# include <zstd.h>
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
# define QT_USE_MMAP
# include <sys/mman.h>
#endif
//#define DEBUG_RESOURCE_MATCH
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// Symbols used by code generated by RCC.
// They cause compilation errors if the RCC content couldn't
// be interpreted by this QtCore version.
#if defined(__ELF__) || defined(__APPLE__) // same as RCC generates
# define RCC_FEATURE_SYMBOL(feature) \
extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature; \
const quint8 qt_resourceFeature ## feature = 0;
#else
# define RCC_FEATURE_SYMBOL(feature) \
Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; }
#endif
#ifndef QT_NO_COMPRESS
RCC_FEATURE_SYMBOL(Zlib)
#endif
#if QT_CONFIG(zstd)
RCC_FEATURE_SYMBOL(Zstd)
#endif
#undef RCC_FEATURE_SYMBOL
class QStringSplitter
{
public:
explicit QStringSplitter(QStringView sv)
: m_data(sv.data()), m_len(sv.size())
{
}
inline bool hasNext()
{
while (m_pos < m_len && m_data[m_pos] == m_splitChar)
++m_pos;
return m_pos < m_len;
}
inline QStringView next()
{
const qsizetype start = m_pos;
while (m_pos < m_len && m_data[m_pos] != m_splitChar)
++m_pos;
return QStringView(m_data + start, m_pos - start);
}
const QChar *m_data;
qsizetype m_len;
qsizetype m_pos = 0;
QChar m_splitChar = u'/';
};
// resource glue
class QResourceRoot
{
public:
enum Flags {
// must match rcc.h
Compressed = 0x01,
Directory = 0x02,
CompressedZstd = 0x04
};
private:
const uchar *tree, *names, *payloads;
int version;
inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element
uint hash(int node) const;
QString name(int node) const;
short flags(int node) const;
public:
mutable QAtomicInt ref;
inline QResourceRoot(): tree(nullptr), names(nullptr), payloads(nullptr), version(0) {}
inline QResourceRoot(int version, const uchar *t, const uchar *n, const uchar *d) { setSource(version, t, n, d); }
virtual ~QResourceRoot() { }
int findNode(const QString &path, const QLocale &locale=QLocale()) const;
inline bool isContainer(int node) const { return flags(node) & Directory; }
QResource::Compression compressionAlgo(int node)
{
uint compressionFlags = flags(node) & (Compressed | CompressedZstd);
if (compressionFlags == Compressed)
return QResource::ZlibCompression;
if (compressionFlags == CompressedZstd)
return QResource::ZstdCompression;
return QResource::NoCompression;
}
const uchar *data(int node, qint64 *size) const;
quint64 lastModified(int node) const;
QStringList children(int node) const;
virtual QString mappingRoot() const { return QString(); }
bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
inline bool operator==(const QResourceRoot &other) const
{ return tree == other.tree && names == other.names && payloads == other.payloads && version == other.version; }
inline bool operator!=(const QResourceRoot &other) const
{ return !operator==(other); }
enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
virtual ResourceRootType type() const { return Resource_Builtin; }
protected:
inline void setSource(int v, const uchar *t, const uchar *n, const uchar *d) {
tree = t;
names = n;
payloads = d;
version = v;
}
};
static QString cleanPath(const QString &_path)
{
QString path = QDir::cleanPath(_path);
// QDir::cleanPath does not remove two trailing slashes under _Windows_
// due to support for UNC paths. Remove those manually.
if (path.startsWith("//"_L1))
path.remove(0, 1);
return path;
}
Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE);
typedef QList<QResourceRoot*> ResourceList;
struct QResourceGlobalData
{
QRecursiveMutex resourceMutex;
ResourceList resourceList;
};
Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
static inline QRecursiveMutex &resourceMutex()
{ return resourceGlobalData->resourceMutex; }
static inline ResourceList *resourceList()
{ return &resourceGlobalData->resourceList; }
/*!
\class QResource
\inmodule QtCore
\brief The QResource class provides an interface for reading directly from resources.
\ingroup io
\reentrant
\since 4.2
QResource is an object that represents a set of data (and possibly
children) relating to a single resource entity. QResource gives direct
access to the bytes in their raw format. In this way direct access
allows reading data without buffer copying or indirection. Indirection
is often useful when interacting with the resource entity as if it is a
file, this can be achieved with QFile. The data and children behind a
QResource are normally compiled into an application/library, but it is
also possible to load a resource at runtime. When loaded at run time
the resource file will be loaded as one big set of data and then given
out in pieces via references into the resource tree.
A QResource can either be loaded with an absolute path, either treated
as a file system rooted with a \c{/} character, or in resource notation
rooted with a \c{:} character. A relative resource can also be opened
which will be found in the list of paths returned by QDir::searchPaths().
A QResource that is representing a file will have data backing it, this
data can possibly be compressed, in which case qUncompress() must be
used to access the real data; this happens implicitly when accessed
through a QFile. A QResource that is representing a directory will have
only children and no data.
\section1 Dynamic Resource Loading
A resource can be left out of an application's binary and loaded when
it is needed at run-time by using the registerResource() function. The
resource file passed into registerResource() must be a binary resource
as created by rcc. Further information about binary resources can be
found in \l{The Qt Resource System} documentation.
This can often be useful when loading a large set of application icons
that may change based on a setting, or that can be edited by a user and
later recreated. The resource is immediately loaded into memory, either
as a result of a single file read operation, or as a memory mapped file.
This approach can prove to be a significant performance gain as only a
single file will be loaded, and pieces of data will be given out via the
path requested in setFileName().
The unregisterResource() function removes a reference to a particular
file. If there are QResource objects that currently reference resources related
to the unregistered file, they will continue to be valid but the resource
file itself will be removed from the resource roots, and thus no further
QResource can be created pointing into this resource data. The resource
itself will be unmapped from memory when the last QResource that points
to it is destroyed.
\sa {The Qt Resource System}, QFile, QDir, QFileInfo
*/
/*!
\enum QResource::Compression
\since 5.13
This enum is used by compressionAlgorithm() to indicate which algorithm the
RCC tool used to compress the payload.
\value NoCompression Contents are not compressed
\value ZlibCompression Contents are compressed using \l{https://zlib.net}{zlib} and can
be decompressed using the qUncompress() function.
\value ZstdCompression Contents are compressed using \l{Zstandard Site}{zstd}. To
decompress, use the \c{ZSTD_decompress} function from the zstd
library.
\sa compressionAlgorithm()
*/
class QResourcePrivate {
public:
inline QResourcePrivate(QResource *_q) : q_ptr(_q) { clear(); }
inline ~QResourcePrivate() { clear(); }
void ensureInitialized() const;
void ensureChildren() const;
qint64 uncompressedSize() const Q_DECL_PURE_FUNCTION;
qsizetype decompress(char *buffer, qsizetype bufferSize) const;
bool load(const QString &file);
void clear();
QLocale locale;
QString fileName, absoluteFilePath;
QList<QResourceRoot *> related;
mutable qint64 size;
mutable quint64 lastModified;
mutable const uchar *data;
mutable QStringList children;
mutable quint8 compressionAlgo;
bool container;
/* 2 or 6 padding bytes */
QResource *q_ptr;
Q_DECLARE_PUBLIC(QResource)
};
void QResourcePrivate::clear()
{
absoluteFilePath.clear();
compressionAlgo = QResource::NoCompression;
data = nullptr;
size = 0;
children.clear();
lastModified = 0;
container = 0;
for (int i = 0; i < related.size(); ++i) {
QResourceRoot *root = related.at(i);
if (!root->ref.deref())
delete root;
}
related.clear();
}
bool QResourcePrivate::load(const QString &file)
{
related.clear();
const auto locker = qt_scoped_lock(resourceMutex());
const ResourceList *list = resourceList();
QString cleaned = cleanPath(file);
for (int i = 0; i < list->size(); ++i) {
QResourceRoot *res = list->at(i);
const int node = res->findNode(cleaned, locale);
if (node != -1) {
if (related.isEmpty()) {
container = res->isContainer(node);
if (!container) {
data = res->data(node, &size);
compressionAlgo = res->compressionAlgo(node);
} else {
data = nullptr;
size = 0;
compressionAlgo = QResource::NoCompression;
}
lastModified = res->lastModified(node);
} else if (res->isContainer(node) != container) {
qWarning("QResourceInfo: Resource [%s] has both data and children!",
file.toLatin1().constData());
}
res->ref.ref();
related.append(res);
} else if (res->mappingRootSubdir(file)) {
container = true;
data = nullptr;
size = 0;
compressionAlgo = QResource::NoCompression;
lastModified = 0;
res->ref.ref();
related.append(res);
}
}
return !related.isEmpty();
}
void QResourcePrivate::ensureInitialized() const
{
if (!related.isEmpty())
return;
QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
if (fileName == ":"_L1)
that->fileName += u'/';
that->absoluteFilePath = fileName;
if (!that->absoluteFilePath.startsWith(u':'))
that->absoluteFilePath.prepend(u':');
QStringView path(fileName);
if (path.startsWith(u':'))
path = path.mid(1);
if (path.startsWith(u'/')) {
that->load(path.toString());
} else {
// Should we search QDir::searchPath() before falling back to root ?
const QString searchPath(u'/' + path);
if (that->load(searchPath))
that->absoluteFilePath = u':' + searchPath;
}
}
void QResourcePrivate::ensureChildren() const
{
ensureInitialized();
if (!children.isEmpty() || !container || related.isEmpty())
return;
QString path = absoluteFilePath, k;
if (path.startsWith(u':'))
path = path.mid(1);
QDuplicateTracker<QString> kids(related.size());
QString cleaned = cleanPath(path);
for (int i = 0; i < related.size(); ++i) {
QResourceRoot *res = related.at(i);
if (res->mappingRootSubdir(path, &k) && !k.isEmpty()) {
if (!kids.hasSeen(k))
children += k;
} else {
const int node = res->findNode(cleaned);
if (node != -1) {
QStringList related_children = res->children(node);
for (int kid = 0; kid < related_children.size(); ++kid) {
k = related_children.at(kid);
if (!kids.hasSeen(k))
children += k;
}
}
}
}
}
qint64 QResourcePrivate::uncompressedSize() const
{
switch (compressionAlgo) {
case QResource::NoCompression:
return size;
case QResource::ZlibCompression:
#ifndef QT_NO_COMPRESS
if (size_t(size) >= sizeof(quint32))
return qFromBigEndian<quint32>(data);
#else
Q_ASSERT(!"QResource: Qt built without support for Zlib compression");
Q_UNREACHABLE();
#endif
break;
case QResource::ZstdCompression: {
#if QT_CONFIG(zstd)
size_t n = ZSTD_getFrameContentSize(data, size);
return ZSTD_isError(n) ? -1 : qint64(n);
#else
// This should not happen because we've refused to load such resource
Q_ASSERT(!"QResource: Qt built without support for Zstd compression");
Q_UNREACHABLE();
#endif
}
}
return -1;
}
qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const
{
Q_ASSERT(data);
#if defined(QT_NO_COMPRESS) && !QT_CONFIG(zstd)
Q_UNUSED(buffer);
Q_UNUSED(bufferSize);
#endif
switch (compressionAlgo) {
case QResource::NoCompression:
Q_UNREACHABLE();
break;
case QResource::ZlibCompression: {
#ifndef QT_NO_COMPRESS
uLong len = uLong(bufferSize);
int res = ::uncompress(reinterpret_cast<Bytef *>(buffer), &len, data + sizeof(quint32),
uLong(size - sizeof(quint32)));
if (res != Z_OK) {
qWarning("QResource: error decompressing zlib content (%d)", res);
return -1;
}
return len;
#else
Q_UNREACHABLE();
#endif
}
case QResource::ZstdCompression: {
#if QT_CONFIG(zstd)
size_t usize = ZSTD_decompress(buffer, bufferSize, data, size);
if (ZSTD_isError(usize)) {
qWarning("QResource: error decompressing zstd content: %s", ZSTD_getErrorName(usize));
return -1;
}
return usize;
#else
Q_UNREACHABLE();
#endif
}
}
return -1;
}
/*!
Constructs a QResource pointing to \a file. \a locale is used to
load a specific localization of a resource data.
\sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale()
*/
QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this))
{
Q_D(QResource);
d->fileName = file;
d->locale = locale;
}
/*!
Releases the resources of the QResource object.
*/
QResource::~QResource()
{
}
/*!
Sets a QResource to only load the localization of resource to for \a
locale. If a resource for the specific locale is not found then the
C locale is used.
\sa setFileName()
*/
void QResource::setLocale(const QLocale &locale)
{
Q_D(QResource);
d->clear();
d->locale = locale;
}
/*!
Returns the locale used to locate the data for the QResource.
*/
QLocale QResource::locale() const
{
Q_D(const QResource);
return d->locale;
}
/*!
Sets a QResource to point to \a file. \a file can either be absolute,
in which case it is opened directly, if relative then the file will be
tried to be found in QDir::searchPaths().
\sa absoluteFilePath()
*/
void QResource::setFileName(const QString &file)
{
Q_D(QResource);
d->clear();
d->fileName = file;
}
/*!
Returns the full path to the file that this QResource represents as it
was passed.
\sa absoluteFilePath()
*/
QString QResource::fileName() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->fileName;
}
/*!
Returns the real path that this QResource represents, if the resource
was found via the QDir::searchPaths() it will be indicated in the path.
\sa fileName()
*/
QString QResource::absoluteFilePath() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->absoluteFilePath;
}
/*!
Returns \c true if the resource really exists in the resource hierarchy,
false otherwise.
*/
bool QResource::isValid() const
{
Q_D(const QResource);
d->ensureInitialized();
return !d->related.isEmpty();
}
/*!
\fn bool QResource::isFile() const
Returns \c true if the resource represents a file and thus has data
backing it, false if it represents a directory.
\sa isDir()
*/
/*!
\since 5.13
Returns the compression type that this resource is compressed with, if any.
If it is not compressed, this function returns QResource::NoCompression.
If this function returns QResource::ZlibCompression, you may decompress the
data using the qUncompress() function. Up until Qt 5.13, this was the only
possible compression algorithm.
If this function returns QResource::ZstdCompression, you need to use the
Zstandard library functions (\c{<zstd.h>} header). Qt does not provide a
wrapper.
See \l{http://facebook.github.io/zstd/zstd_manual.html}{Zstandard manual}.
\sa data(), isFile()
*/
QResource::Compression QResource::compressionAlgorithm() const
{
Q_D(const QResource);
d->ensureInitialized();
return Compression(d->compressionAlgo);
}
/*!
Returns the size of the stored data backing the resource.
If the resource is compressed, this function returns the size of the
compressed data. See uncompressedSize() for the uncompressed size.
\sa data(), uncompressedSize(), isFile()
*/
qint64 QResource::size() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->size;
}
/*!
\since 5.15
Returns the size of the data in this resource. If the data was not
compressed, this function returns the same as size(). If it was, then this
function extracts the size of the original uncompressed data from the
stored stream.
\sa size(), uncompressedData(), isFile()
*/
qint64 QResource::uncompressedSize() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->uncompressedSize();
}
/*!
Returns direct access to a segment of read-only data, that this resource
represents. If the resource is compressed, the data returned is also
compressed. The caller must then decompress the data or use
uncompressedData(). If the resource is a directory, \c nullptr is returned.
\sa uncompressedData(), size(), isFile()
*/
const uchar *QResource::data() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->data;
}
/*!
\since 5.15
Returns the resource data, decompressing it first, if the data was stored
compressed. If the resource is a directory or an error occurs while
decompressing, a null QByteArray is returned.
\note If the data was compressed, this function will decompress every time
it is called. The result is not cached between calls.
\sa uncompressedSize(), size(), compressionAlgorithm(), isFile()
*/
QByteArray QResource::uncompressedData() const
{
Q_D(const QResource);
qint64 n = uncompressedSize();
if (n < 0)
return QByteArray();
if (n > std::numeric_limits<QByteArray::size_type>::max()) {
qWarning("QResource: compressed content does not fit into a QByteArray; use QFile instead");
return QByteArray();
}
if (d->compressionAlgo == NoCompression)
return QByteArray::fromRawData(reinterpret_cast<const char *>(d->data), n);
// decompress
QByteArray result(n, Qt::Uninitialized);
n = d->decompress(result.data(), n);
if (n < 0)
result.clear();
else
result.truncate(n);
return result;
}
/*!
\since 5.8
Returns the date and time when the file was last modified before
packaging into a resource.
*/
QDateTime QResource::lastModified() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->lastModified ? QDateTime::fromMSecsSinceEpoch(d->lastModified) : QDateTime();
}
/*!
Returns \c true if the resource represents a directory and thus may have
children() in it, false if it represents a file.
\sa isFile()
*/
bool QResource::isDir() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->container;
}
/*!
Returns a list of all resources in this directory, if the resource
represents a file the list will be empty.
\sa isDir()
*/
QStringList QResource::children() const
{
Q_D(const QResource);
d->ensureChildren();
return d->children;
}
inline uint QResourceRoot::hash(int node) const
{
if (!node) // root
return 0;
const int offset = findOffset(node);
qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
name_offset += 2; // jump past name length
return qFromBigEndian<quint32>(names + name_offset);
}
inline QString QResourceRoot::name(int node) const
{
if (!node) // root
return QString();
const int offset = findOffset(node);
QString ret;
qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
quint16 name_length = qFromBigEndian<qint16>(names + name_offset);
name_offset += 2;
name_offset += 4; // jump past hash
ret.resize(name_length);
QChar *strData = ret.data();
qFromBigEndian<char16_t>(names + name_offset, name_length, strData);
return ret;
}
int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
{
QString path = _path;
{
QString root = mappingRoot();
if (!root.isEmpty()) {
if (root == path) {
path = u'/';
} else {
if (!root.endsWith(u'/'))
root += u'/';
if (path.size() >= root.size() && path.startsWith(root))
path = path.mid(root.size() - 1);
if (path.isEmpty())
path = u'/';
}
}
}
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
#endif
if (path == "/"_L1)
return 0;
// the root node is always first
qint32 child_count = qFromBigEndian<qint32>(tree + 6);
qint32 child = qFromBigEndian<qint32>(tree + 10);
// now iterate up the tree
int node = -1;
QStringSplitter splitter(path);
while (child_count && splitter.hasNext()) {
QStringView segment = splitter.next();
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << " CHILDREN" << segment;
for (int j = 0; j < child_count; ++j) {
qDebug() << " " << child + j << " :: " << name(child + j);
}
#endif
const uint h = qt_hash(segment);
// do the binary search for the hash
int l = 0, r = child_count - 1;
int sub_node = (l + r + 1) / 2;
while (r != l) {
const uint sub_node_hash = hash(child + sub_node);
if (h == sub_node_hash)
break;
else if (h < sub_node_hash)
r = sub_node - 1;
else
l = sub_node;
sub_node = (l + r + 1) / 2;
}
sub_node += child;
// now do the "harder" compares
bool found = false;
if (hash(sub_node) == h) {
while (sub_node > child && hash(sub_node - 1) == h) // backup for collisions
--sub_node;
for (; sub_node < child + child_count && hash(sub_node) == h;
++sub_node) { // here we go...
if (name(sub_node) == segment) {
found = true;
int offset = findOffset(sub_node);
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << " TRY" << sub_node << name(sub_node) << offset;
#endif
offset += 4; // jump past name
const qint16 flags = qFromBigEndian<qint16>(tree + offset);
offset += 2;
if (!splitter.hasNext()) {
if (!(flags & Directory)) {
const qint16 territory = qFromBigEndian<qint16>(tree + offset);
offset += 2;
const qint16 language = qFromBigEndian<qint16>(tree + offset);
offset += 2;
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << " " << "LOCALE" << country << language;
#endif
if (territory == locale.territory() && language == locale.language()) {
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
#endif
return sub_node;
} else if ((territory == QLocale::AnyTerritory
&& language == locale.language())
|| (territory == QLocale::AnyTerritory
&& language == QLocale::C
&& node == -1)) {
node = sub_node;
}
continue;
} else {
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
#endif
return sub_node;
}
}
if (!(flags & Directory))
return -1;
child_count = qFromBigEndian<qint32>(tree + offset);
offset += 4;
child = qFromBigEndian<qint32>(tree + offset);
break;
}
}
}
if (!found)
break;
}
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
#endif
return node;
}
short QResourceRoot::flags(int node) const
{
if (node == -1)
return 0;
const int offset = findOffset(node) + 4; // jump past name
return qFromBigEndian<qint16>(tree + offset);
}
const uchar *QResourceRoot::data(int node, qint64 *size) const
{
if (node == -1) {
*size = 0;
return nullptr;
}
int offset = findOffset(node) + 4; // jump past name
const qint16 flags = qFromBigEndian<qint16>(tree + offset);
offset += 2;
offset += 4; // jump past locale
if (!(flags & Directory)) {
const qint32 data_offset = qFromBigEndian<qint32>(tree + offset);
const quint32 data_length = qFromBigEndian<quint32>(payloads + data_offset);
const uchar *ret = payloads + data_offset + 4;
*size = data_length;
return ret;
}
*size = 0;
return nullptr;
}
quint64 QResourceRoot::lastModified(int node) const
{
if (node == -1 || version < 0x02)
return 0;
const int offset = findOffset(node) + 14;
return qFromBigEndian<quint64>(tree + offset);
}
QStringList QResourceRoot::children(int node) const
{
if (node == -1)
return QStringList();
int offset = findOffset(node) + 4; // jump past name
const qint16 flags = qFromBigEndian<qint16>(tree + offset);
offset += 2;
QStringList ret;
if (flags & Directory) {
const qint32 child_count = qFromBigEndian<qint32>(tree + offset);
offset += 4;
const qint32 child_off = qFromBigEndian<qint32>(tree + offset);
ret.reserve(child_count);
for (int i = child_off; i < child_off + child_count; ++i)
ret << name(i);
}
return ret;
}
bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
{
const QString root = mappingRoot();
if (root.isEmpty())
return false;
QStringSplitter rootIt(root);
QStringSplitter pathIt(path);
while (rootIt.hasNext()) {
if (pathIt.hasNext()) {
if (rootIt.next() != pathIt.next()) // mismatch
return false;
} else {
// end of path, but not of root:
if (match)
*match = rootIt.next().toString();
return true;
}
}
// end of root
return !pathIt.hasNext();
}
Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
const unsigned char *name, const unsigned char *data)
{
if (resourceGlobalData.isDestroyed())
return false;
const auto locker = qt_scoped_lock(resourceMutex());
ResourceList *list = resourceList();
if (version >= 0x01 && version <= 0x3) {
bool found = false;
QResourceRoot res(version, tree, name, data);
for (int i = 0; i < list->size(); ++i) {
if (*list->at(i) == res) {
found = true;
break;
}
}
if (!found) {
QResourceRoot *root = new QResourceRoot(version, tree, name, data);
root->ref.ref();
list->append(root);
}
return true;
}
return false;
}
Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
const unsigned char *name, const unsigned char *data)
{
if (resourceGlobalData.isDestroyed())
return false;
const auto locker = qt_scoped_lock(resourceMutex());
if (version >= 0x01 && version <= 0x3) {
QResourceRoot res(version, tree, name, data);
ResourceList *list = resourceList();
for (int i = 0; i < list->size();) {
if (*list->at(i) == res) {
QResourceRoot *root = list->takeAt(i);
if (!root->ref.deref())
delete root;
} else {
++i;
}
}
return true;
}
return false;
}
// run time resource creation
class QDynamicBufferResourceRoot : public QResourceRoot
{
QString root;
const uchar *buffer;
public:
inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
inline ~QDynamicBufferResourceRoot() { }
inline const uchar *mappingBuffer() const { return buffer; }
QString mappingRoot() const override { return root; }
ResourceRootType type() const override { return Resource_Buffer; }
// size == -1 means "unknown"
bool registerSelf(const uchar *b, qsizetype size)
{
// 5 int "pointers"
if (size >= 0 && size < 20)
return false;
// setup the data now
int offset = 0;
// magic number
if (b[offset + 0] != 'q' || b[offset + 1] != 'r' || b[offset + 2] != 'e'
|| b[offset + 3] != 's') {
return false;
}
offset += 4;
const int version = qFromBigEndian<qint32>(b + offset);
offset += 4;
const int tree_offset = qFromBigEndian<qint32>(b + offset);
offset += 4;
const int data_offset = qFromBigEndian<qint32>(b + offset);
offset += 4;
const int name_offset = qFromBigEndian<qint32>(b + offset);
offset += 4;
quint32 file_flags = 0;
if (version >= 3) {
file_flags = qFromBigEndian<qint32>(b + offset);
offset += 4;
}
// Some sanity checking for sizes. This is _not_ a security measure.
if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
return false;
// And some sanity checking for features
quint32 acceptableFlags = 0;
#ifndef QT_NO_COMPRESS
acceptableFlags |= Compressed;
#endif
if (QT_CONFIG(zstd))
acceptableFlags |= CompressedZstd;
if (file_flags & ~acceptableFlags)
return false;
if (version >= 0x01 && version <= 0x03) {
buffer = b;
setSource(version, b + tree_offset, b + name_offset, b + data_offset);
return true;
}
return false;
}
};
class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
{
QString fileName;
// for mmap'ed files, this is what needs to be unmapped.
uchar *unmapPointer;
qsizetype unmapLength;
public:
QDynamicFileResourceRoot(const QString &_root)
: 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
{
delete[] mappingBuffer();
}
}
QString mappingFile() const { return fileName; }
ResourceRootType type() const override { return Resource_File; }
bool registerSelf(const QString &f);
};
#ifndef MAP_FILE
# define MAP_FILE 0
#endif
#ifndef MAP_FAILED
# define MAP_FAILED reinterpret_cast<void *>(-1)
#endif
bool QDynamicFileResourceRoot::registerSelf(const QString &f)
{
bool fromMM = false;
uchar *data = nullptr;
qsizetype data_len = 0;
#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;
}
}
QT_CLOSE(fd);
}
#endif // QT_USE_MMAP
if (!data) {
QFile file(f);
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));
}
}
if (!ok) {
delete[] data;
data = nullptr;
data_len = 0;
return false;
}
fromMM = false;
}
if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) {
if (fromMM) {
unmapPointer = data;
unmapLength = data_len;
}
fileName = f;
return true;
}
return false;
}
static QString qt_resource_fixResourceRoot(QString r)
{
if (!r.isEmpty()) {
if (r.startsWith(u':'))
r = r.mid(1);
if (!r.isEmpty())
r = QDir::cleanPath(r);
}
return r;
}
/*!
\fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
Registers the resource with the given \a rccFileName at the location in the
resource tree specified by \a mapRoot, and returns \c true if the file is
successfully opened; otherwise returns \c false.
\sa unregisterResource()
*/
bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
if (!r.isEmpty() && r[0] != u'/') {
qWarning("QDir::registerResource: Registering a resource [%ls] must be rooted in an "
"absolute path (start with /) [%ls]",
qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
return false;
}
QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
if (root->registerSelf(rccFilename)) {
root->ref.ref();
const auto locker = qt_scoped_lock(resourceMutex());
resourceList()->append(root);
return true;
}
delete root;
return false;
}
/*!
\fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
Unregisters the resource with the given \a rccFileName at the location in
the resource tree specified by \a mapRoot, and returns \c true if the
resource is successfully unloaded and no references exist for the
resource; otherwise returns \c false.
\sa registerResource()
*/
bool QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
const auto locker = qt_scoped_lock(resourceMutex());
ResourceList *list = resourceList();
for (int i = 0; i < list->size(); ++i) {
QResourceRoot *res = list->at(i);
if (res->type() == QResourceRoot::Resource_File) {
QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot *>(res);
if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
list->removeAt(i);
if (!root->ref.deref()) {
delete root;
return true;
}
return false;
}
}
}
return false;
}
/*!
\fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
\since 4.3
Registers the resource with the given \a rccData at the location in the
resource tree specified by \a mapRoot, and returns \c true if the file is
successfully opened; otherwise returns \c false.
\warning The data must remain valid throughout the life of any QFile
that may reference the resource data.
\sa unregisterResource()
*/
bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
if (!r.isEmpty() && r[0] != u'/') {
qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an "
"absolute path (start with /) [%ls]",
rccData, qUtf16Printable(resourceRoot));
return false;
}
QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
if (root->registerSelf(rccData, -1)) {
root->ref.ref();
const auto locker = qt_scoped_lock(resourceMutex());
resourceList()->append(root);
return true;
}
delete root;
return false;
}
/*!
\fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
\since 4.3
Unregisters the resource with the given \a rccData at the location in the
resource tree specified by \a mapRoot, and returns \c true if the resource is
successfully unloaded and no references exist into the resource; otherwise returns \c false.
\sa registerResource()
*/
bool QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
const auto locker = qt_scoped_lock(resourceMutex());
ResourceList *list = resourceList();
for (int i = 0; i < list->size(); ++i) {
QResourceRoot *res = list->at(i);
if (res->type() == QResourceRoot::Resource_Buffer) {
QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot *>(res);
if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
list->removeAt(i);
if (!root->ref.deref()) {
delete root;
return true;
}
return false;
}
}
}
return false;
}
#if !defined(QT_BOOTSTRAPPED)
// resource engine
class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate
{
protected:
Q_DECLARE_PUBLIC(QResourceFileEngine)
private:
uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
bool unmap(uchar *ptr);
void uncompress() const;
qint64 offset;
QResource resource;
mutable QByteArray uncompressed;
protected:
QResourceFileEnginePrivate() : offset(0) { }
};
bool QResourceFileEngine::caseSensitive() const
{
return true;
}
QResourceFileEngine::QResourceFileEngine(const QString &file) :
QAbstractFileEngine(*new QResourceFileEnginePrivate)
{
Q_D(QResourceFileEngine);
d->resource.setFileName(file);
}
QResourceFileEngine::~QResourceFileEngine()
{
}
void QResourceFileEngine::setFileName(const QString &file)
{
Q_D(QResourceFileEngine);
d->resource.setFileName(file);
}
bool QResourceFileEngine::open(QIODevice::OpenMode flags,
std::optional<QFile::Permissions> permissions)
{
Q_UNUSED(permissions);
Q_D(QResourceFileEngine);
if (d->resource.fileName().isEmpty()) {
qWarning("QResourceFileEngine::open: Missing file name");
return false;
}
if (flags & QIODevice::WriteOnly)
return false;
if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
d->uncompress();
if (d->uncompressed.isNull()) {
d->errorString = QSystemError::stdString(EIO);
return false;
}
}
if (!d->resource.isValid()) {
d->errorString = QSystemError::stdString(ENOENT);
return false;
}
return true;
}
bool QResourceFileEngine::close()
{
Q_D(QResourceFileEngine);
d->offset = 0;
return true;
}
bool QResourceFileEngine::flush()
{
return true;
}
qint64 QResourceFileEngine::read(char *data, qint64 len)
{
Q_D(QResourceFileEngine);
if (len > size() - d->offset)
len = size() - d->offset;
if (len <= 0)
return 0;
if (!d->uncompressed.isNull())
memcpy(data, d->uncompressed.constData() + d->offset, len);
else
memcpy(data, d->resource.data() + d->offset, len);
d->offset += len;
return len;
}
qint64 QResourceFileEngine::size() const
{
Q_D(const QResourceFileEngine);
return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
}
qint64 QResourceFileEngine::pos() const
{
Q_D(const QResourceFileEngine);
return d->offset;
}
bool QResourceFileEngine::atEnd() const
{
Q_D(const QResourceFileEngine);
if (!d->resource.isValid())
return true;
return d->offset == size();
}
bool QResourceFileEngine::seek(qint64 pos)
{
Q_D(QResourceFileEngine);
if (!d->resource.isValid())
return false;
if (d->offset > size())
return false;
d->offset = pos;
return true;
}
QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
{
Q_D(const QResourceFileEngine);
QAbstractFileEngine::FileFlags ret;
if (!d->resource.isValid())
return ret;
if (type & PermsMask)
ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm | ReadUserPerm | ReadGroupPerm
| ReadOtherPerm);
if (type & TypesMask) {
if (d->resource.isDir())
ret |= DirectoryType;
else
ret |= FileType;
}
if (type & FlagsMask) {
ret |= ExistsFlag;
if (d->resource.absoluteFilePath() == ":/"_L1)
ret |= RootFlag;
}
return ret;
}
QString QResourceFileEngine::fileName(FileName file) const
{
Q_D(const QResourceFileEngine);
if (file == BaseName) {
const qsizetype slash = d->resource.fileName().lastIndexOf(u'/');
if (slash == -1)
return d->resource.fileName();
return d->resource.fileName().mid(slash + 1);
} else if (file == PathName || file == AbsolutePathName) {
const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
: d->resource.fileName();
const qsizetype slash = path.lastIndexOf(u'/');
if (slash == -1)
return ":"_L1;
else if (slash <= 1)
return ":/"_L1;
return path.left(slash);
} else if (file == CanonicalName || file == CanonicalPathName) {
const QString absoluteFilePath = d->resource.absoluteFilePath();
if (file == CanonicalPathName) {
const qsizetype slash = absoluteFilePath.lastIndexOf(u'/');
if (slash != -1)
return absoluteFilePath.left(slash);
}
return absoluteFilePath;
}
return d->resource.fileName();
}
uint QResourceFileEngine::ownerId(FileOwner) const
{
static const uint nobodyID = static_cast<uint>(-2);
return nobodyID;
}
QDateTime QResourceFileEngine::fileTime(FileTime time) const
{
Q_D(const QResourceFileEngine);
if (time == ModificationTime)
return d->resource.lastModified();
return QDateTime();
}
/*!
\internal
*/
QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
const QStringList &filterNames)
{
return new QResourceFileEngineIterator(filters, filterNames);
}
/*!
\internal
*/
QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
{
return nullptr;
}
bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
{
Q_D(QResourceFileEngine);
if (extension == MapExtension) {
const auto *options = static_cast<const MapExtensionOption *>(option);
auto *returnValue = static_cast<MapExtensionReturn *>(output);
returnValue->address = d->map(options->offset, options->size, options->flags);
return (returnValue->address != nullptr);
}
if (extension == UnMapExtension) {
const auto *options = static_cast<const UnMapExtensionOption *>(option);
return d->unmap(options->address);
}
return false;
}
bool QResourceFileEngine::supportsExtension(Extension extension) const
{
return (extension == UnMapExtension || extension == MapExtension);
}
uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
{
Q_Q(QResourceFileEngine);
Q_UNUSED(flags);
qint64 max = resource.uncompressedSize();
qint64 end;
if (offset < 0 || size <= 0 || !resource.isValid() ||
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;
address = reinterpret_cast<const uchar *>(uncompressed.constData());
}
return const_cast<uchar *>(address) + offset;
}
bool QResourceFileEnginePrivate::unmap(uchar *ptr)
{
Q_UNUSED(ptr);
return true;
}
void QResourceFileEnginePrivate::uncompress() const
{
if (resource.compressionAlgorithm() == QResource::NoCompression
|| !uncompressed.isEmpty() || resource.size() == 0)
return; // nothing to do
uncompressed = resource.uncompressedData();
}
#endif // !defined(QT_BOOTSTRAPPED)
QT_END_NAMESPACE