Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qresource.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2020 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
6#include "qresource.h"
7#include "qresource_p.h"
9#include "qset.h"
10#include <private/qlocking_p.h>
11#include "qdebug.h"
12#include "qlocale.h"
13#include "qglobal.h"
14#include "qlist.h"
15#include "qdatetime.h"
16#include "qbytearray.h"
17#include "qstringlist.h"
18#include "qendian.h"
19#include <qshareddata.h>
20#include <qplatformdefs.h>
21#include <qendian.h>
22#include "private/qabstractfileengine_p.h"
23#include "private/qduplicatetracker_p.h"
24#include "private/qnumeric_p.h"
25#include "private/qsimd_p.h"
26#include "private/qtools_p.h"
27#include "private/qsystemerror_p.h"
28
29#ifndef QT_NO_COMPRESS
30# include <zconf.h>
31# include <zlib.h>
32#endif
33#if QT_CONFIG(zstd)
34# include <zstd.h>
35#endif
36
37#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
38# define QT_USE_MMAP
39# include <sys/mman.h>
40# ifdef Q_OS_LINUX
41// since 5.7, so define in case we're being compiled with older kernel headers
42# define MREMAP_DONTUNMAP 4
43# elif defined(Q_OS_DARWIN)
44# include <mach/mach.h>
45# include <mach/vm_map.h>
46# endif
47#endif
48#ifdef Q_OS_WIN
49# include <qt_windows.h>
50#endif
51
52//#define DEBUG_RESOURCE_MATCH
53
54QT_BEGIN_NAMESPACE
55
56using namespace Qt::StringLiterals;
57
58// Symbols used by code generated by RCC.
59// They cause compilation errors if the RCC content couldn't
60// be interpreted by this QtCore version.
61#if defined(__ELF__) || defined(__APPLE__) // same as RCC generates
62# define RCC_FEATURE_SYMBOL(feature)
63 extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature;
64 const quint8 qt_resourceFeature ## feature = 0;
65#else
66# define RCC_FEATURE_SYMBOL(feature)
67 Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; }
68#endif
69
70#ifndef QT_NO_COMPRESS
72#endif
73#if QT_CONFIG(zstd)
74RCC_FEATURE_SYMBOL(Zstd)
75#endif
76
77#undef RCC_FEATURE_SYMBOL
78
79namespace {
80class QStringSplitter
81{
82public:
83 explicit QStringSplitter(QStringView sv)
84 : m_data(sv.data()), m_len(sv.size())
85 {
86 }
87
88 inline bool hasNext()
89 {
90 while (m_pos < m_len && m_data[m_pos] == m_splitChar)
91 ++m_pos;
92 return m_pos < m_len;
93 }
94
95 inline QStringView next()
96 {
97 const qsizetype start = m_pos;
98 while (m_pos < m_len && m_data[m_pos] != m_splitChar)
99 ++m_pos;
100 return QStringView(m_data + start, m_pos - start);
101 }
102
103 const QChar *m_data;
104 qsizetype m_len;
105 qsizetype m_pos = 0;
106 QChar m_splitChar = u'/';
107};
108
109// resource glue
110class QResourceRoot
111{
112public:
113 enum Flags {
114 // must match rcc.h
115 Compressed = 0x01,
116 Directory = 0x02,
117 CompressedZstd = 0x04
118 };
119
120private:
121 const uchar *tree, *names, *payloads;
122 int version;
123 inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element
124 uint hash(int node) const;
125 QString name(int node) const;
126 short flags(int node) const;
127public:
128 mutable QAtomicInt ref;
129
130 inline QResourceRoot(): tree(nullptr), names(nullptr), payloads(nullptr), version(0) {}
131 inline QResourceRoot(int version, const uchar *t, const uchar *n, const uchar *d) { setSource(version, t, n, d); }
132 virtual ~QResourceRoot() = default;
133 Q_DISABLE_COPY_MOVE(QResourceRoot)
134 int findNode(const QString &path, const QLocale &locale=QLocale()) const;
135 inline bool isContainer(int node) const { return flags(node) & Directory; }
136 QResource::Compression compressionAlgo(int node)
137 {
138 uint compressionFlags = flags(node) & (Compressed | CompressedZstd);
139 if (compressionFlags == Compressed)
140 return QResource::ZlibCompression;
141 if (compressionFlags == CompressedZstd)
142 return QResource::ZstdCompression;
143 return QResource::NoCompression;
144 }
145 const uchar *data(int node, qint64 *size) const;
146 qint64 lastModified(int node) const;
147 QStringList children(int node) const;
148 virtual QString mappingRoot() const { return QString(); }
149 bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
150 inline bool operator==(const QResourceRoot &other) const
151 { return tree == other.tree && names == other.names && payloads == other.payloads && version == other.version; }
152 inline bool operator!=(const QResourceRoot &other) const
153 { return !operator==(other); }
154 enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
155 virtual ResourceRootType type() const { return Resource_Builtin; }
156
157protected:
158 inline void setSource(int v, const uchar *t, const uchar *n, const uchar *d) {
159 tree = t;
160 names = n;
161 payloads = d;
162 version = v;
163 }
164};
165
166static QString cleanPath(const QString &_path)
167{
168 QString path = QDir::cleanPath(_path);
169 // QDir::cleanPath does not remove two trailing slashes under _Windows_
170 // due to support for UNC paths. Remove those manually.
171 if (path.startsWith("//"_L1))
172 path.remove(0, 1);
173 return path;
174}
175} // unnamed namespace
176
177typedef QList<QResourceRoot*> ResourceList;
178namespace {
179struct QResourceGlobalData
180{
181 QRecursiveMutex resourceMutex;
182 ResourceList resourceList;
183};
184}
185Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
186
188{ return resourceGlobalData->resourceMutex; }
189
191{ return &resourceGlobalData->resourceList; }
192
193/*!
194 \class QResource
195 \inmodule QtCore
196 \brief The QResource class provides an interface for reading directly from resources.
197
198 \ingroup io
199
200 \reentrant
201 \since 4.2
202
203 QResource is an object that represents a set of data (and possibly
204 children) relating to a single resource entity. QResource gives direct
205 access to the bytes in their raw format. In this way direct access
206 allows reading data without buffer copying or indirection. Indirection
207 is often useful when interacting with the resource entity as if it is a
208 file, this can be achieved with QFile. The data and children behind a
209 QResource are normally compiled into an application/library, but it is
210 also possible to load a resource at runtime. When loaded at run time
211 the resource file will be loaded as one big set of data and then given
212 out in pieces via references into the resource tree.
213
214 A QResource can either be loaded with an absolute path, either treated
215 as a file system rooted with a \c{/} character, or in resource notation
216 rooted with a \c{:} character. A relative resource can also be opened
217 which will be found in the list of paths returned by QDir::searchPaths().
218
219 A QResource that is representing a file will have data backing it, this
220 data can possibly be compressed, in which case qUncompress() must be
221 used to access the real data; this happens implicitly when accessed
222 through a QFile. A QResource that is representing a directory will have
223 only children and no data.
224
225 \section1 Dynamic Resource Loading
226
227 A resource can be left out of an application's binary and loaded when
228 it is needed at run-time by using the registerResource() function. The
229 resource file passed into registerResource() must be a binary resource
230 as created by rcc. Further information about binary resources can be
231 found in \l{The Qt Resource System} documentation.
232
233 This can often be useful when loading a large set of application icons
234 that may change based on a setting, or that can be edited by a user and
235 later recreated. The resource is immediately loaded into memory, either
236 as a result of a single file read operation, or as a memory mapped file.
237
238 This approach can prove to be a significant performance gain as only a
239 single file will be loaded, and pieces of data will be given out via the
240 path requested in setFileName().
241
242 The unregisterResource() function removes a reference to a particular
243 file. If there are QResource objects that currently reference resources related
244 to the unregistered file, they will continue to be valid but the resource
245 file itself will be removed from the resource roots, and thus no further
246 QResource can be created pointing into this resource data. The resource
247 itself will be unmapped from memory when the last QResource that points
248 to it is destroyed.
249
250 \section2 Corruption and Security
251
252 The QResource class performs some checks on the file passed to determine
253 whether it is supported by the current version of Qt. Those tests are only
254 to check the file header does not request features (such as Zstandard
255 decompression) that have not been compiled in or that the file is not of a
256 future version of Qt. They do not confirm the validity of the entire file.
257
258 QResource should not be used on files whose provenance cannot be trusted.
259 Applications should be designed to attempt to load only resource files
260 whose provenance is at least as trustworthy as that of the application
261 itself or its plugins.
262
263 \sa {The Qt Resource System}, QFile, QDir, QFileInfo
264*/
265
266/*!
267 \enum QResource::Compression
268 \since 5.13
269
270 This enum is used by compressionAlgorithm() to indicate which algorithm the
271 RCC tool used to compress the payload.
272
273 \value NoCompression Contents are not compressed
274 \value ZlibCompression Contents are compressed using \l{https://zlib.net}{zlib} and can
275 be decompressed using the qUncompress() function.
276 \value ZstdCompression Contents are compressed using \l{Zstandard Site}{zstd}. To
277 decompress, use the \c{ZSTD_decompress} function from the zstd
278 library.
279
280 \sa compressionAlgorithm()
281*/
282
284public:
285 inline QResourcePrivate(QResource *_q) : q_ptr(_q) { clear(); }
287
288 void ensureInitialized() const;
289 void ensureChildren() const;
290 Q_DECL_PURE_FUNCTION qint64 uncompressedSize() const;
291 qsizetype decompress(char *buffer, qsizetype bufferSize) const;
292
293 bool load(const QString &file);
294 void clear();
295
296 static bool mayRemapData(const QResource &resource);
297
298 QLocale locale;
300 QList<QResourceRoot *> related;
303 const uchar *data;
307 /* 2 or 6 padding bytes */
308
310 Q_DECLARE_PUBLIC(QResource)
311};
312
314{
315 absoluteFilePath.clear();
316 compressionAlgo = QResource::NoCompression;
317 data = nullptr;
318 size = 0;
319 children.clear();
320 lastModified = 0;
321 container = 0;
322 for (int i = 0; i < related.size(); ++i) {
323 QResourceRoot *root = related.at(i);
324 if (!root->ref.deref())
325 delete root;
326 }
327 related.clear();
328}
329
330bool QResourcePrivate::load(const QString &file)
331{
332 related.clear();
333 const auto locker = qt_scoped_lock(resourceMutex());
334 const ResourceList *list = resourceList();
335 QString cleaned = cleanPath(file);
336 for (int i = 0; i < list->size(); ++i) {
337 QResourceRoot *res = list->at(i);
338 const int node = res->findNode(cleaned, locale);
339 if (node != -1) {
340 if (related.isEmpty()) {
341 container = res->isContainer(node);
342 if (!container) {
343 data = res->data(node, &size);
344 compressionAlgo = res->compressionAlgo(node);
345 } else {
346 data = nullptr;
347 size = 0;
348 compressionAlgo = QResource::NoCompression;
349 }
350 lastModified = res->lastModified(node);
351 } else if (res->isContainer(node) != container) {
352 qWarning("QResourceInfo: Resource [%s] has both data and children!",
353 file.toLatin1().constData());
354 }
355 res->ref.ref();
356 related.append(res);
357 } else if (res->mappingRootSubdir(file)) {
358 container = true;
359 data = nullptr;
360 size = 0;
361 compressionAlgo = QResource::NoCompression;
362 lastModified = 0;
363 res->ref.ref();
364 related.append(res);
365 }
366 }
367 return !related.isEmpty();
368}
369
371{
372 if (!related.isEmpty())
373 return;
374 if (resourceGlobalData.isDestroyed())
375 return;
376 QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
377 if (fileName == ":"_L1)
378 that->fileName += u'/';
379 that->absoluteFilePath = fileName;
380 if (!that->absoluteFilePath.startsWith(u':'))
381 that->absoluteFilePath.prepend(u':');
382
383 QStringView path(fileName);
384 if (path.startsWith(u':'))
385 path = path.mid(1);
386
387 if (path.startsWith(u'/')) {
388 that->load(path.toString());
389 } else {
390 // Should we search QDir::searchPath() before falling back to root ?
391 const QString searchPath(u'/' + path);
392 if (that->load(searchPath))
393 that->absoluteFilePath = u':' + searchPath;
394 }
395}
396
398{
400 if (!children.isEmpty() || !container || related.isEmpty())
401 return;
402
403 QString path = absoluteFilePath, k;
404 if (path.startsWith(u':'))
405 path = path.mid(1);
406 QDuplicateTracker<QString> kids(related.size());
407 QString cleaned = cleanPath(path);
408 for (int i = 0; i < related.size(); ++i) {
409 QResourceRoot *res = related.at(i);
410 if (res->mappingRootSubdir(path, &k) && !k.isEmpty()) {
411 if (!kids.hasSeen(k))
412 children += k;
413 } else {
414 const int node = res->findNode(cleaned);
415 if (node != -1) {
416 QStringList related_children = res->children(node);
417 for (int kid = 0; kid < related_children.size(); ++kid) {
418 k = related_children.at(kid);
419 if (!kids.hasSeen(k))
420 children += k;
421 }
422 }
423 }
424 }
425}
426
427qint64 QResourcePrivate::uncompressedSize() const
428{
429 switch (compressionAlgo) {
430 case QResource::NoCompression:
431 return size;
432
433 case QResource::ZlibCompression:
434#ifndef QT_NO_COMPRESS
435 if (size_t(size) >= sizeof(quint32))
436 return qFromBigEndian<quint32>(data);
437#else
438 Q_ASSERT(!"QResource: Qt built without support for Zlib compression");
439 Q_UNREACHABLE();
440#endif
441 break;
442
443 case QResource::ZstdCompression: {
444#if QT_CONFIG(zstd)
445 size_t n = ZSTD_getFrameContentSize(data, size);
446 return ZSTD_isError(n) ? -1 : qint64(n);
447#else
448 // This should not happen because we've refused to load such resource
449 Q_ASSERT(!"QResource: Qt built without support for Zstd compression");
450 Q_UNREACHABLE();
451#endif
452 }
453 }
454 return -1;
455}
456
457qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const
458{
459 Q_ASSERT(data);
460#if defined(QT_NO_COMPRESS) && !QT_CONFIG(zstd)
461 Q_UNUSED(buffer);
462 Q_UNUSED(bufferSize);
463#endif
464
465 switch (compressionAlgo) {
466 case QResource::NoCompression:
467 Q_UNREACHABLE();
468 break;
469
470 case QResource::ZlibCompression: {
471#ifndef QT_NO_COMPRESS
472 uLong len = uLong(bufferSize);
473 int res = ::uncompress(reinterpret_cast<Bytef *>(buffer), &len, data + sizeof(quint32),
474 uLong(size - sizeof(quint32)));
475 if (res != Z_OK) {
476 qWarning("QResource: error decompressing zlib content (%d)", res);
477 return -1;
478 }
479 return len;
480#else
481 Q_UNREACHABLE();
482#endif
483 }
484
485 case QResource::ZstdCompression: {
486#if QT_CONFIG(zstd)
487 size_t usize = ZSTD_decompress(buffer, bufferSize, data, size);
488 if (ZSTD_isError(usize)) {
489 qWarning("QResource: error decompressing zstd content: %s", ZSTD_getErrorName(usize));
490 return -1;
491 }
492 return usize;
493#else
494 Q_UNREACHABLE();
495#endif
496 }
497 }
498
499 return -1;
500}
501
502/*!
503 Constructs a QResource pointing to \a file. \a locale is used to
504 load a specific localization of a resource data.
505
506 \sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale()
507*/
508
509QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this))
510{
511 Q_D(QResource);
512 d->fileName = file;
513 d->locale = locale;
514}
515
516/*!
517 Releases the resources of the QResource object.
518*/
519QResource::~QResource()
520{
521}
522
523/*!
524 Sets a QResource to only load the localization of resource to for \a
525 locale. If a resource for the specific locale is not found then the
526 C locale is used.
527
528 \sa setFileName()
529*/
530
531void QResource::setLocale(const QLocale &locale)
532{
533 Q_D(QResource);
534 d->clear();
535 d->locale = locale;
536}
537
538/*!
539 Returns the locale used to locate the data for the QResource.
540*/
541
542QLocale QResource::locale() const
543{
544 Q_D(const QResource);
545 return d->locale;
546}
547
548/*!
549 Sets a QResource to point to \a file. \a file can either be absolute,
550 in which case it is opened directly, if relative then the file will be
551 tried to be found in QDir::searchPaths().
552
553 \sa absoluteFilePath()
554*/
555
556void QResource::setFileName(const QString &file)
557{
558 Q_D(QResource);
559 d->clear();
560 d->fileName = file;
561}
562
563/*!
564 Returns the full path to the file that this QResource represents as it
565 was passed.
566
567 \sa absoluteFilePath()
568*/
569
570QString QResource::fileName() const
571{
572 Q_D(const QResource);
573 d->ensureInitialized();
574 return d->fileName;
575}
576
577/*!
578 Returns the real path that this QResource represents, if the resource
579 was found via the QDir::searchPaths() it will be indicated in the path.
580
581 \sa fileName()
582*/
583
584QString QResource::absoluteFilePath() const
585{
586 Q_D(const QResource);
587 d->ensureInitialized();
588 return d->absoluteFilePath;
589}
590
591/*!
592 Returns \c true if the resource really exists in the resource hierarchy,
593 false otherwise.
594
595*/
596
597bool QResource::isValid() const
598{
599 Q_D(const QResource);
600 d->ensureInitialized();
601 return !d->related.isEmpty();
602}
603
604/*!
605 \fn bool QResource::isFile() const
606
607 Returns \c true if the resource represents a file and thus has data
608 backing it, false if it represents a directory.
609
610 \sa isDir()
611*/
612
613/*!
614 \since 5.13
615
616 Returns the compression type that this resource is compressed with, if any.
617 If it is not compressed, this function returns QResource::NoCompression.
618
619 If this function returns QResource::ZlibCompression, you may decompress the
620 data using the qUncompress() function. Up until Qt 5.13, this was the only
621 possible compression algorithm.
622
623 If this function returns QResource::ZstdCompression, you need to use the
624 Zstandard library functions (\c{<zstd.h>} header). Qt does not provide a
625 wrapper.
626
627 See \l{http://facebook.github.io/zstd/zstd_manual.html}{Zstandard manual}.
628
629 \sa data(), isFile()
630*/
631QResource::Compression QResource::compressionAlgorithm() const
632{
633 Q_D(const QResource);
634 d->ensureInitialized();
635 return Compression(d->compressionAlgo);
636}
637
638/*!
639 Returns the size of the stored data backing the resource.
640
641 If the resource is compressed, this function returns the size of the
642 compressed data. See uncompressedSize() for the uncompressed size.
643
644 \sa data(), uncompressedSize(), isFile()
645*/
646
647qint64 QResource::size() const
648{
649 Q_D(const QResource);
650 d->ensureInitialized();
651 return d->size;
652}
653
654/*!
655 \since 5.15
656
657 Returns the size of the data in this resource. If the data was not
658 compressed, this function returns the same as size(). If it was, then this
659 function extracts the size of the original uncompressed data from the
660 stored stream.
661
662 \sa size(), uncompressedData(), isFile()
663*/
664qint64 QResource::uncompressedSize() const
665{
666 Q_D(const QResource);
667 d->ensureInitialized();
668 return d->uncompressedSize();
669}
670
671/*!
672 Returns direct access to a segment of read-only data, that this resource
673 represents. If the resource is compressed, the data returned is also
674 compressed. The caller must then decompress the data or use
675 uncompressedData(). If the resource is a directory, \c nullptr is returned.
676
677 \sa uncompressedData(), size(), isFile()
678*/
679
680const uchar *QResource::data() const
681{
682 Q_D(const QResource);
683 d->ensureInitialized();
684 return d->data;
685}
686
687/*!
688 \since 5.15
689
690 Returns the resource data, decompressing it first, if the data was stored
691 compressed. If the resource is a directory or an error occurs while
692 decompressing, a null QByteArray is returned.
693
694 \note If the data was compressed, this function will decompress every time
695 it is called. The result is not cached between calls.
696
697 \sa uncompressedSize(), size(), compressionAlgorithm(), isFile()
698*/
699
700QByteArray QResource::uncompressedData() const
701{
702 Q_D(const QResource);
703 qint64 n = uncompressedSize();
704 if (n < 0)
705 return QByteArray();
706 if (n > std::numeric_limits<QByteArray::size_type>::max()) {
707 qWarning("QResource: compressed content does not fit into a QByteArray; use QFile instead");
708 return QByteArray();
709 }
710 if (d->compressionAlgo == NoCompression)
711 return QByteArray::fromRawData(reinterpret_cast<const char *>(d->data), n);
712
713 // decompress
714 QByteArray result(n, Qt::Uninitialized);
715 n = d->decompress(result.data(), n);
716 if (n < 0)
717 result.clear();
718 else
719 result.truncate(n);
720 return result;
721}
722
723/*!
724 \since 5.8
725
726 Returns the date and time when the file was last modified before
727 packaging into a resource.
728*/
729QDateTime QResource::lastModified() const
730{
731 Q_D(const QResource);
732 d->ensureInitialized();
733 return d->lastModified ? QDateTime::fromMSecsSinceEpoch(d->lastModified) : QDateTime();
734}
735
736/*!
737 Returns \c true if the resource represents a directory and thus may have
738 children() in it, false if it represents a file.
739
740 \sa isFile()
741*/
742
743bool QResource::isDir() const
744{
745 Q_D(const QResource);
746 d->ensureInitialized();
747 return d->container;
748}
749
750/*!
751 Returns a list of all resources in this directory, if the resource
752 represents a file the list will be empty.
753
754 \sa isDir()
755*/
756
757QStringList QResource::children() const
758{
759 Q_D(const QResource);
760 d->ensureChildren();
761 return d->children;
762}
763
764inline uint QResourceRoot::hash(int node) const
765{
766 if (!node) // root
767 return 0;
768 const int offset = findOffset(node);
769 qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
770 name_offset += 2; // jump past name length
771 return qFromBigEndian<quint32>(names + name_offset);
772}
773inline QString QResourceRoot::name(int node) const
774{
775 if (!node) // root
776 return QString();
777 const int offset = findOffset(node);
778
779 QString ret;
780 qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
781 quint16 name_length = qFromBigEndian<qint16>(names + name_offset);
782 name_offset += 2;
783 name_offset += 4; // jump past hash
784
785 ret.resize(name_length);
786 QChar *strData = ret.data();
787 qFromBigEndian<char16_t>(names + name_offset, name_length, strData);
788 return ret;
789}
790
791int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
792{
793 QString path = _path;
794 {
795 QString root = mappingRoot();
796 if (!root.isEmpty()) {
797 if (root == path) {
798 path = u'/';
799 } else {
800 if (!root.endsWith(u'/'))
801 root += u'/';
802 if (path.size() >= root.size() && path.startsWith(root))
803 path = path.mid(root.size() - 1);
804 if (path.isEmpty())
805 path = u'/';
806 }
807 }
808 }
809#ifdef DEBUG_RESOURCE_MATCH
810 qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
811#endif
812
813 if (path == "/"_L1)
814 return 0;
815
816 // the root node is always first
817 qint32 child_count = qFromBigEndian<qint32>(tree + 6);
818 qint32 child = qFromBigEndian<qint32>(tree + 10);
819
820 // now iterate up the tree
821 int node = -1;
822
823 QStringSplitter splitter(path);
824 while (child_count && splitter.hasNext()) {
825 QStringView segment = splitter.next();
826
827#ifdef DEBUG_RESOURCE_MATCH
828 qDebug() << " CHILDREN" << segment;
829 for (int j = 0; j < child_count; ++j) {
830 qDebug() << " " << child + j << " :: " << name(child + j);
831 }
832#endif
833 const uint h = qt_hash(segment);
834
835 // do the binary search for the hash
836 int l = 0, r = child_count - 1;
837 int sub_node = (l + r + 1) / 2;
838 while (r != l) {
839 const uint sub_node_hash = hash(child + sub_node);
840 if (h == sub_node_hash)
841 break;
842 else if (h < sub_node_hash)
843 r = sub_node - 1;
844 else
845 l = sub_node;
846 sub_node = (l + r + 1) / 2;
847 }
848 sub_node += child;
849
850 // now do the "harder" compares
851 bool found = false;
852 if (hash(sub_node) == h) {
853 while (sub_node > child && hash(sub_node - 1) == h) // backup for collisions
854 --sub_node;
855 for (; sub_node < child + child_count && hash(sub_node) == h;
856 ++sub_node) { // here we go...
857 if (name(sub_node) == segment) {
858 found = true;
859 int offset = findOffset(sub_node);
860#ifdef DEBUG_RESOURCE_MATCH
861 qDebug() << " TRY" << sub_node << name(sub_node) << offset;
862#endif
863 offset += 4; // jump past name
864
865 const qint16 flags = qFromBigEndian<qint16>(tree + offset);
866 offset += 2;
867
868 if (!splitter.hasNext()) {
869 if (!(flags & Directory)) {
870 const qint16 territory = qFromBigEndian<qint16>(tree + offset);
871 offset += 2;
872
873 const qint16 language = qFromBigEndian<qint16>(tree + offset);
874 offset += 2;
875#ifdef DEBUG_RESOURCE_MATCH
876 qDebug() << " " << "LOCALE" << country << language;
877#endif
878 if (territory == locale.territory() && language == locale.language()) {
879#ifdef DEBUG_RESOURCE_MATCH
880 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
881#endif
882 return sub_node;
883 } else if ((territory == QLocale::AnyTerritory
884 && language == locale.language())
885 || (territory == QLocale::AnyTerritory
886 && language == QLocale::C
887 && node == -1)) {
888 node = sub_node;
889 }
890 continue;
891 } else {
892#ifdef DEBUG_RESOURCE_MATCH
893 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
894#endif
895
896 return sub_node;
897 }
898 }
899
900 if (!(flags & Directory))
901 return -1;
902
903 child_count = qFromBigEndian<qint32>(tree + offset);
904 offset += 4;
905 child = qFromBigEndian<qint32>(tree + offset);
906 break;
907 }
908 }
909 }
910 if (!found)
911 break;
912 }
913#ifdef DEBUG_RESOURCE_MATCH
914 qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
915#endif
916 return node;
917}
918short QResourceRoot::flags(int node) const
919{
920 if (node == -1)
921 return 0;
922 const int offset = findOffset(node) + 4; // jump past name
923 return qFromBigEndian<qint16>(tree + offset);
924}
925const uchar *QResourceRoot::data(int node, qint64 *size) const
926{
927 if (node == -1) {
928 *size = 0;
929 return nullptr;
930 }
931 int offset = findOffset(node) + 4; // jump past name
932
933 const qint16 flags = qFromBigEndian<qint16>(tree + offset);
934 offset += 2;
935
936 offset += 4; // jump past locale
937
938 if (!(flags & Directory)) {
939 const qint32 data_offset = qFromBigEndian<qint32>(tree + offset);
940 const quint32 data_length = qFromBigEndian<quint32>(payloads + data_offset);
941 const uchar *ret = payloads + data_offset + 4;
942 *size = data_length;
943 return ret;
944 }
945 *size = 0;
946 return nullptr;
947}
948
949qint64 QResourceRoot::lastModified(int node) const
950{
951 if (node == -1 || version < 0x02)
952 return 0;
953
954 const int offset = findOffset(node) + 14;
955
956 return qFromBigEndian<qint64>(tree + offset);
957}
958
959QStringList QResourceRoot::children(int node) const
960{
961 if (node == -1)
962 return QStringList();
963 int offset = findOffset(node) + 4; // jump past name
964
965 const qint16 flags = qFromBigEndian<qint16>(tree + offset);
966 offset += 2;
967
968 QStringList ret;
969 if (flags & Directory) {
970 const qint32 child_count = qFromBigEndian<qint32>(tree + offset);
971 offset += 4;
972 const qint32 child_off = qFromBigEndian<qint32>(tree + offset);
973 ret.reserve(child_count);
974 for (int i = child_off; i < child_off + child_count; ++i)
975 ret << name(i);
976 }
977 return ret;
978}
979bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
980{
981 const QString root = mappingRoot();
982 if (root.isEmpty())
983 return false;
984
985 QStringSplitter rootIt(root);
986 QStringSplitter pathIt(path);
987 while (rootIt.hasNext()) {
988 if (pathIt.hasNext()) {
989 if (rootIt.next() != pathIt.next()) // mismatch
990 return false;
991 } else {
992 // end of path, but not of root:
993 if (match)
994 *match = rootIt.next().toString();
995 return true;
996 }
997 }
998 // end of root
999 return !pathIt.hasNext();
1000}
1001
1002Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
1003 const unsigned char *name, const unsigned char *data)
1004{
1005 if (resourceGlobalData.isDestroyed())
1006 return false;
1007 const auto locker = qt_scoped_lock(resourceMutex());
1009 if (version >= 0x01 && version <= 0x3) {
1010 bool found = false;
1011 QResourceRoot res(version, tree, name, data);
1012 for (int i = 0; i < list->size(); ++i) {
1013 if (*list->at(i) == res) {
1014 found = true;
1015 break;
1016 }
1017 }
1018 if (!found) {
1019 QResourceRoot *root = new QResourceRoot(version, tree, name, data);
1020 root->ref.ref();
1021 list->append(root);
1022 }
1023 return true;
1024 }
1025 return false;
1026}
1027
1028Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
1029 const unsigned char *name, const unsigned char *data)
1030{
1031 if (resourceGlobalData.isDestroyed())
1032 return false;
1033
1034 const auto locker = qt_scoped_lock(resourceMutex());
1035 if (version >= 0x01 && version <= 0x3) {
1036 QResourceRoot res(version, tree, name, data);
1038 for (int i = 0; i < list->size();) {
1039 if (*list->at(i) == res) {
1040 QResourceRoot *root = list->takeAt(i);
1041 if (!root->ref.deref())
1042 delete root;
1043 } else {
1044 ++i;
1045 }
1046 }
1047 return true;
1048 }
1049 return false;
1050}
1051
1052namespace {
1053// run time resource creation
1054class QDynamicBufferResourceRoot : public QResourceRoot
1055{
1056 QString root;
1057 const uchar *buffer;
1058
1059public:
1060 inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
1061 inline ~QDynamicBufferResourceRoot() { }
1062 inline const uchar *mappingBuffer() const { return buffer; }
1063 QString mappingRoot() const override { return root; }
1064 ResourceRootType type() const override { return Resource_Buffer; }
1065
1066 // size == -1 means "unknown"
1067 bool registerSelf(const uchar *b, qsizetype size)
1068 {
1069 // 5 int "pointers"
1070 if (size >= 0 && size < 20)
1071 return false;
1072
1073 // setup the data now
1074 int offset = 0;
1075
1076 // magic number
1077 if (b[offset + 0] != 'q' || b[offset + 1] != 'r' || b[offset + 2] != 'e'
1078 || b[offset + 3] != 's') {
1079 return false;
1080 }
1081 offset += 4;
1082
1083 const int version = qFromBigEndian<qint32>(b + offset);
1084 offset += 4;
1085
1086 const int tree_offset = qFromBigEndian<qint32>(b + offset);
1087 offset += 4;
1088
1089 const int data_offset = qFromBigEndian<qint32>(b + offset);
1090 offset += 4;
1091
1092 const int name_offset = qFromBigEndian<qint32>(b + offset);
1093 offset += 4;
1094
1095 quint32 file_flags = 0;
1096 if (version >= 3) {
1097 file_flags = qFromBigEndian<qint32>(b + offset);
1098 offset += 4;
1099 }
1100
1101 // Some sanity checking for sizes. This is _not_ a security measure.
1102 if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
1103 return false;
1104
1105 // And some sanity checking for features
1106 quint32 acceptableFlags = 0;
1107#ifndef QT_NO_COMPRESS
1108 acceptableFlags |= Compressed;
1109#endif
1110 if (QT_CONFIG(zstd))
1111 acceptableFlags |= CompressedZstd;
1112 if (file_flags & ~acceptableFlags)
1113 return false;
1114
1115 if (version >= 0x01 && version <= 0x03) {
1116 buffer = b;
1117 setSource(version, b + tree_offset, b + name_offset, b + data_offset);
1118 return true;
1119 }
1120 return false;
1121 }
1122};
1123
1124class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
1125{
1126public:
1127 static uchar *map_sys(QFile &file, qint64 base, qsizetype size);
1128 static void unmap_sys(void *base, qsizetype size);
1129
1130private:
1131 QString fileName;
1132 // for mmap'ed files, this is what needs to be unmapped.
1133 uchar *unmapPointer;
1134 qsizetype unmapLength;
1135
1136public:
1137 QDynamicFileResourceRoot(const QString &_root)
1138 : QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
1139 { }
1140 ~QDynamicFileResourceRoot() {
1141 if (wasMemoryMapped())
1142 unmap_sys(unmapPointer, unmapLength);
1143 else
1144 delete[] mappingBuffer();
1145 }
1146 QString mappingFile() const { return fileName; }
1147 ResourceRootType type() const override { return Resource_File; }
1148 bool wasMemoryMapped() const { return unmapPointer; }
1149
1150 bool registerSelf(const QString &f);
1151};
1152} // unnamed namespace
1153
1154#ifndef MAP_FILE
1155# define MAP_FILE 0
1156#endif
1157#ifndef MAP_FAILED
1158# define MAP_FAILED reinterpret_cast<void *>(-1)
1159#endif
1160
1161void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size)
1162{
1163#if defined(QT_USE_MMAP)
1164 munmap(base, size);
1165#elif defined(Q_OS_WIN)
1166 Q_UNUSED(size)
1167 UnmapViewOfFile(reinterpret_cast<void *>(base));
1168#endif
1169}
1170
1171// Note: caller must ensure \a offset and \a size are acceptable to the OS.
1172uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size)
1173{
1174 Q_ASSERT(file.isOpen());
1175 void *ptr = nullptr;
1176 if (size < 0)
1177 size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)());
1178 int fd = file.handle();
1179 if (fd < 0)
1180 return nullptr;
1181 // We don't use QFile::map() here because we want to dispose of the QFile object
1182#if defined(QT_USE_MMAP)
1183 int protection = PROT_READ; // read-only memory
1184 int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
1185 ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset);
1186 if (ptr == MAP_FAILED)
1187 ptr = nullptr;
1188#elif defined(Q_OS_WIN)
1189 HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
1190 if (fileHandle != INVALID_HANDLE_VALUE) {
1191 HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0);
1192 if (mapHandle) {
1193 ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size);
1194 CloseHandle(mapHandle);
1195 }
1196 }
1197#endif // QT_USE_MMAP
1198 return static_cast<uchar *>(ptr);
1199}
1200
1201bool QDynamicFileResourceRoot::registerSelf(const QString &f)
1202{
1203 QFile file(f);
1204 if (!file.open(QIODevice::ReadOnly))
1205 return false;
1206
1207 qint64 data_len = file.size();
1208 if (data_len > std::numeric_limits<qsizetype>::max())
1209 return false;
1210
1211 uchar *data = map_sys(file, 0, data_len);
1212 bool fromMM = !!data;
1213
1214 if (!fromMM) {
1215 bool ok = false;
1216 data = new uchar[data_len];
1217 ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
1218 if (!ok) {
1219 delete[] data;
1220 data = nullptr;
1221 data_len = 0;
1222 return false;
1223 }
1224 }
1225 if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) {
1226 if (fromMM) {
1227 unmapPointer = data;
1228 unmapLength = data_len;
1229 }
1230 fileName = f;
1231 return true;
1232 }
1233 return false;
1234}
1235
1236static QString qt_resource_fixResourceRoot(QString r)
1237{
1238 if (!r.isEmpty()) {
1239 if (r.startsWith(u':'))
1240 r = r.mid(1);
1241 if (!r.isEmpty())
1242 r = QDir::cleanPath(r);
1243 }
1244 return r;
1245}
1246
1247/*!
1248 \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
1249
1250 Registers the resource with the given \a rccFileName at the location in the
1251 resource tree specified by \a mapRoot, and returns \c true if the file is
1252 successfully opened; otherwise returns \c false.
1253
1254 \sa unregisterResource()
1255*/
1256
1257bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
1258{
1259 QString r = qt_resource_fixResourceRoot(resourceRoot);
1260 if (!r.isEmpty() && r[0] != u'/') {
1261 qWarning("QDir::registerResource: Registering a resource [%ls] must be rooted in an "
1262 "absolute path (start with /) [%ls]",
1263 qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
1264 return false;
1265 }
1266
1267 QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
1268 if (root->registerSelf(rccFilename)) {
1269 root->ref.ref();
1270 const auto locker = qt_scoped_lock(resourceMutex());
1271 resourceList()->append(root);
1272 return true;
1273 }
1274 delete root;
1275 return false;
1276}
1277
1278/*!
1279 \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
1280
1281 Unregisters the resource with the given \a rccFileName at the location in
1282 the resource tree specified by \a mapRoot, and returns \c true if the
1283 resource is successfully unloaded and no references exist for the
1284 resource; otherwise returns \c false.
1285
1286 \sa registerResource()
1287*/
1288
1289bool QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
1290{
1291 QString r = qt_resource_fixResourceRoot(resourceRoot);
1292
1293 const auto locker = qt_scoped_lock(resourceMutex());
1294 ResourceList *list = resourceList();
1295 for (int i = 0; i < list->size(); ++i) {
1296 QResourceRoot *res = list->at(i);
1297 if (res->type() == QResourceRoot::Resource_File) {
1298 QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot *>(res);
1299 if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
1300 list->removeAt(i);
1301 if (!root->ref.deref()) {
1302 delete root;
1303 return true;
1304 }
1305 return false;
1306 }
1307 }
1308 }
1309 return false;
1310}
1311
1312/*!
1313 \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
1314 \since 4.3
1315
1316 Registers the resource with the given \a rccData at the location in the
1317 resource tree specified by \a mapRoot, and returns \c true if the file is
1318 successfully opened; otherwise returns \c false.
1319
1320 \warning The data must remain valid throughout the life of any QFile
1321 that may reference the resource data.
1322
1323 \sa unregisterResource()
1324*/
1325
1326bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
1327{
1328 QString r = qt_resource_fixResourceRoot(resourceRoot);
1329 if (!r.isEmpty() && r[0] != u'/') {
1330 qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an "
1331 "absolute path (start with /) [%ls]",
1332 rccData, qUtf16Printable(resourceRoot));
1333 return false;
1334 }
1335
1336 QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
1337 if (root->registerSelf(rccData, -1)) {
1338 root->ref.ref();
1339 const auto locker = qt_scoped_lock(resourceMutex());
1340 resourceList()->append(root);
1341 return true;
1342 }
1343 delete root;
1344 return false;
1345}
1346
1347/*!
1348 \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
1349 \since 4.3
1350
1351 Unregisters the resource with the given \a rccData at the location in the
1352 resource tree specified by \a mapRoot, and returns \c true if the resource is
1353 successfully unloaded and no references exist into the resource; otherwise returns \c false.
1354
1355 \sa registerResource()
1356*/
1357
1358bool QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
1359{
1360 QString r = qt_resource_fixResourceRoot(resourceRoot);
1361
1362 const auto locker = qt_scoped_lock(resourceMutex());
1363 ResourceList *list = resourceList();
1364 for (int i = 0; i < list->size(); ++i) {
1365 QResourceRoot *res = list->at(i);
1366 if (res->type() == QResourceRoot::Resource_Buffer) {
1367 QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot *>(res);
1368 if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
1369 list->removeAt(i);
1370 if (!root->ref.deref()) {
1371 delete root;
1372 return true;
1373 }
1374 return false;
1375 }
1376 }
1377 }
1378 return false;
1379}
1380
1381#if !defined(QT_BOOTSTRAPPED)
1382// resource engine
1384{
1385protected:
1386 Q_DECLARE_PUBLIC(QResourceFileEngine)
1387private:
1389 bool unmap(uchar *ptr);
1390 void uncompress() const;
1391 void mapUncompressed();
1392 bool mapUncompressed_sys();
1393 void unmapUncompressed_sys();
1394 qint64 offset = 0;
1395 QResource resource;
1396 mutable QByteArray uncompressed;
1397 bool mustUnmap = false;
1398
1399 // minimum size for which we'll try to re-open ourselves in mapUncompressed()
1400 static constexpr qsizetype RemapCompressedThreshold = 16384;
1401protected:
1402 QResourceFileEnginePrivate(QAbstractFileEngine *q) :
1404
1406 {
1407 if (mustUnmap)
1408 unmapUncompressed_sys();
1409 }
1410};
1411
1413{
1414 return true;
1415}
1416
1417QResourceFileEngine::QResourceFileEngine(const QString &file) :
1418 QAbstractFileEngine(*new QResourceFileEnginePrivate(this))
1419{
1420 Q_D(QResourceFileEngine);
1421 d->resource.setFileName(file);
1422}
1423
1427
1428void QResourceFileEngine::setFileName(const QString &file)
1429{
1430 Q_D(QResourceFileEngine);
1431 d->resource.setFileName(file);
1432}
1433
1434bool QResourceFileEngine::open(QIODevice::OpenMode flags,
1435 std::optional<QFile::Permissions> permissions)
1436{
1437 Q_UNUSED(permissions);
1438
1439 Q_D(QResourceFileEngine);
1440 if (d->resource.fileName().isEmpty()) {
1441 qWarning("QResourceFileEngine::open: Missing file name");
1442 return false;
1443 }
1444 if (flags & QIODevice::WriteOnly)
1445 return false;
1446 if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
1447 d->uncompress();
1448 if (d->uncompressed.isNull()) {
1449 d->errorString = QSystemError::stdString(EIO);
1450 return false;
1451 }
1452 }
1453 if (!d->resource.isValid()) {
1454 d->errorString = QSystemError::stdString(ENOENT);
1455 return false;
1456 }
1457 return true;
1458}
1459
1461{
1462 Q_D(QResourceFileEngine);
1463 d->offset = 0;
1464 return true;
1465}
1466
1468{
1469 return true;
1470}
1471
1472qint64 QResourceFileEngine::read(char *data, qint64 len)
1473{
1474 Q_D(QResourceFileEngine);
1475 if (len > size() - d->offset)
1476 len = size() - d->offset;
1477 if (len <= 0)
1478 return 0;
1479 if (!d->uncompressed.isNull())
1480 memcpy(data, d->uncompressed.constData() + d->offset, len);
1481 else
1482 memcpy(data, d->resource.data() + d->offset, len);
1483 d->offset += len;
1484 return len;
1485}
1486
1488{
1489 Q_D(const QResourceFileEngine);
1490 return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
1491}
1492
1494{
1495 Q_D(const QResourceFileEngine);
1496 return d->offset;
1497}
1498
1500{
1501 Q_D(const QResourceFileEngine);
1502 if (!d->resource.isValid())
1503 return true;
1504 return d->offset == size();
1505}
1506
1507bool QResourceFileEngine::seek(qint64 pos)
1508{
1509 Q_D(QResourceFileEngine);
1510 if (!d->resource.isValid())
1511 return false;
1512
1513 if (d->offset > size())
1514 return false;
1515 d->offset = pos;
1516 return true;
1517}
1518
1519QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
1520{
1521 Q_D(const QResourceFileEngine);
1522 QAbstractFileEngine::FileFlags ret;
1523 if (!d->resource.isValid())
1524 return ret;
1525
1526 if (type & PermsMask)
1527 ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm | ReadUserPerm | ReadGroupPerm
1528 | ReadOtherPerm);
1529 if (type & TypesMask) {
1530 if (d->resource.isDir())
1531 ret |= DirectoryType;
1532 else
1533 ret |= FileType;
1534 }
1535 if (type & FlagsMask) {
1536 ret |= ExistsFlag;
1537 if (d->resource.absoluteFilePath() == ":/"_L1)
1538 ret |= RootFlag;
1539 }
1540 return ret;
1541}
1542
1543QString QResourceFileEngine::fileName(FileName file) const
1544{
1545 Q_D(const QResourceFileEngine);
1546 if (file == BaseName) {
1547 const qsizetype slash = d->resource.fileName().lastIndexOf(u'/');
1548 if (slash == -1)
1549 return d->resource.fileName();
1550 return d->resource.fileName().mid(slash + 1);
1551 } else if (file == PathName || file == AbsolutePathName) {
1552 const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
1553 : d->resource.fileName();
1554 const qsizetype slash = path.lastIndexOf(u'/');
1555 if (slash == -1)
1556 return ":"_L1;
1557 else if (slash <= 1)
1558 return ":/"_L1;
1559 return path.left(slash);
1560
1561 } else if (file == CanonicalName || file == CanonicalPathName) {
1562 const QString absoluteFilePath = d->resource.absoluteFilePath();
1563 if (file == CanonicalPathName) {
1564 const qsizetype slash = absoluteFilePath.lastIndexOf(u'/');
1565 if (slash != -1)
1566 return absoluteFilePath.left(slash);
1567 }
1568 return absoluteFilePath;
1569 }
1570 return d->resource.fileName();
1571}
1572
1574{
1575 static const uint nobodyID = static_cast<uint>(-2);
1576 return nobodyID;
1577}
1578
1579QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const
1580{
1581 Q_D(const QResourceFileEngine);
1582 if (time == QFile::FileModificationTime)
1583 return d->resource.lastModified();
1584 return QDateTime();
1585}
1586
1587/*!
1588 \internal
1589*/
1590QAbstractFileEngine::IteratorUniquePtr
1591QResourceFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
1592 const QStringList &filterNames)
1593{
1594 return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
1595}
1596
1597bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
1598{
1599 Q_D(QResourceFileEngine);
1600 if (extension == MapExtension) {
1601 const auto *options = static_cast<const MapExtensionOption *>(option);
1602 auto *returnValue = static_cast<MapExtensionReturn *>(output);
1603 returnValue->address = d->map(options->offset, options->size, options->flags);
1604 return (returnValue->address != nullptr);
1605 }
1606 if (extension == UnMapExtension) {
1607 const auto *options = static_cast<const UnMapExtensionOption *>(option);
1608 return d->unmap(options->address);
1609 }
1610 return false;
1611}
1612
1613bool QResourceFileEngine::supportsExtension(Extension extension) const
1614{
1615 return (extension == UnMapExtension || extension == MapExtension);
1616}
1617
1618uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
1619{
1620 Q_Q(QResourceFileEngine);
1621 Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression
1622 || !uncompressed.isNull(), "QFile::map()",
1623 "open() should have uncompressed compressed resources");
1624
1625 qint64 max = resource.uncompressedSize();
1626 qint64 end;
1627 if (offset < 0 || size <= 0 || !resource.isValid() ||
1628 qAddOverflow(offset, size, &end) || end > max) {
1629 q->setError(QFile::UnspecifiedError, QString());
1630 return nullptr;
1631 }
1632
1633 const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin());
1634 if (!uncompressed.isNull())
1635 return const_cast<uchar *>(address) + offset;
1636
1637 // resource was not compressed
1638 address = resource.data();
1639 if (flags & QFile::MapPrivateOption) {
1640 // We need to provide read-write memory
1641 mapUncompressed();
1642 address = reinterpret_cast<const uchar *>(uncompressed.constData());
1643 }
1644
1645 return const_cast<uchar *>(address) + offset;
1646}
1647
1648bool QResourceFileEnginePrivate::unmap(uchar *ptr)
1649{
1650 Q_UNUSED(ptr);
1651 return true;
1652}
1653
1654void QResourceFileEnginePrivate::uncompress() const
1655{
1656 if (resource.compressionAlgorithm() == QResource::NoCompression
1657 || !uncompressed.isEmpty() || resource.size() == 0)
1658 return; // nothing to do
1659 uncompressed = resource.uncompressedData();
1660}
1661
1662void QResourceFileEnginePrivate::mapUncompressed()
1663{
1664 Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression);
1665 if (!uncompressed.isNull())
1666 return; // nothing to do
1667
1668 if (resource.uncompressedSize() >= RemapCompressedThreshold) {
1669 if (mapUncompressed_sys())
1670 return;
1671 }
1672
1673 uncompressed = resource.uncompressedData();
1674 uncompressed.detach();
1675}
1676
1677#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
1678inline bool QResourcePrivate::mayRemapData(const QResource &resource)
1679{
1680 auto d = resource.d_func();
1681
1682 // assumptions from load():
1683 // - d->related is not empty
1684 // - the first item in d->related is the one with our data
1685 // by current construction, it's also the only item
1686 const QResourceRoot *root = d->related.at(0);
1687
1688 switch (root->type()) {
1689 case QResourceRoot::Resource_Builtin:
1690 return true; // always acceptable, memory is read-only
1691 case QResourceRoot::Resource_Buffer:
1692 return false; // never acceptable, memory is heap
1693 case QResourceRoot::Resource_File:
1694 break;
1695 }
1696
1697 auto df = static_cast<const QDynamicFileResourceRoot *>(root);
1698 return df->wasMemoryMapped();
1699}
1700#endif
1701
1702// Returns the page boundaries of where \a location is located in memory.
1703static auto mappingBoundaries(const void *location, qsizetype size)
1704{
1705#ifdef Q_OS_WIN
1706 auto getpagesize = [] {
1707 SYSTEM_INFO sysinfo;
1708 ::GetSystemInfo(&sysinfo);
1709 return sysinfo.dwAllocationGranularity;
1710 };
1711#endif
1712 struct R {
1713 void *begin;
1714 qsizetype size;
1715 qptrdiff offset;
1716 } r;
1717
1718 const quintptr pageMask = getpagesize() - 1;
1719 quintptr data = quintptr(location);
1720 quintptr begin = data & ~pageMask;
1721 quintptr end = (data + size + pageMask) & ~pageMask;
1722 r.begin = reinterpret_cast<void *>(begin);
1723 r.size = end - begin;
1724 r.offset = data & pageMask;
1725 return r;
1726}
1727
1728bool QResourceFileEnginePrivate::mapUncompressed_sys()
1729{
1730 auto r = mappingBoundaries(resource.data(), resource.uncompressedSize());
1731 void *ptr = nullptr;
1732
1733#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
1734 // Use MREMAP_MAYMOVE to tell the kernel to give us a new address and use
1735 // MREMAP_DONTUNMAP (supported since kernel 5.7) to request that it create
1736 // a new mapping of the same pages, instead of moving. We can only do that
1737 // for pages that are read-only, otherwise the kernel replaces the source
1738 // with pages full of nulls.
1739 if (!QResourcePrivate::mayRemapData(resource))
1740 return false;
1741
1742 ptr = mremap(r.begin, r.size, r.size, MREMAP_MAYMOVE | MREMAP_DONTUNMAP);
1743 if (ptr == MAP_FAILED)
1744 return false;
1745
1746 // Allow writing, which the documentation says we allow. This is safe
1747 // because MREMAP_DONTUNMAP only works for private mappings.
1748 if (mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
1749 munmap(ptr, r.size);
1750 return false;
1751 }
1752#elif defined(Q_OS_DARWIN)
1753 mach_port_t self = mach_task_self();
1754 vm_address_t addr = 0;
1755 vm_address_t mask = 0;
1756 bool anywhere = true;
1757 bool copy = true;
1758 vm_prot_t cur_prot = VM_PROT_READ | VM_PROT_WRITE;
1759 vm_prot_t max_prot = VM_PROT_ALL;
1760 kern_return_t res = vm_remap(self, &addr, r.size, mask, anywhere,
1761 self, vm_address_t(r.begin), copy, &cur_prot,
1762 &max_prot, VM_INHERIT_DEFAULT);
1763 if (res != KERN_SUCCESS)
1764 return false;
1765
1766 ptr = reinterpret_cast<void *>(addr);
1767 if ((max_prot & VM_PROT_WRITE) == 0 || mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
1768 munmap(ptr, r.size);
1769 return false;
1770 }
1771#endif
1772
1773 if (!ptr)
1774 return false;
1775 const char *newdata = static_cast<char *>(ptr) + r.offset;
1776 uncompressed = QByteArray::fromRawData(newdata, resource.uncompressedSize());
1777 mustUnmap = true;
1778 return true;
1779}
1780
1781void QResourceFileEnginePrivate::unmapUncompressed_sys()
1782{
1783 auto r = mappingBoundaries(uncompressed.constBegin(), uncompressed.size());
1784 QDynamicFileResourceRoot::unmap_sys(r.begin, r.size);
1785}
1786
1787#endif // !defined(QT_BOOTSTRAPPED)
1788
1789QT_END_NAMESPACE
Definition qlist.h:81
QResourceFileEnginePrivate(QAbstractFileEngine *q)
bool caseSensitive() const override
Should return true if the underlying file system is case-sensitive; otherwise return false.
bool flush() override
Flushes the open file, returning true if successful; otherwise returns false.
qint64 read(char *data, qint64 maxlen) override
Reads a number of characters from the file into data.
bool close() override
Closes the file, returning true if successful; otherwise returns false.
bool open(QIODevice::OpenMode flags, std::optional< QFile::Permissions > permissions) override
Opens the file in the specified mode.
bool seek(qint64) override
Sets the file position to the given offset.
qint64 pos() const override
Returns the current file position.
QString fileName(QAbstractFileEngine::FileName file) const override
Return the file engine's current file name in the format specified by file.
qint64 size() const override
Returns the size of the file.
bool extension(Extension extension, const ExtensionOption *option=nullptr, ExtensionReturn *output=nullptr) override
virtual bool atEnd() const
uint ownerId(FileOwner) const override
If owner is OwnerUser return the ID of the user who owns the file.
bool supportsExtension(Extension extension) const override
void ensureChildren() const
static bool mayRemapData(const QResource &resource)
void ensureInitialized() const
QList< QResourceRoot * > related
QResource * q_ptr
QResourcePrivate(QResource *_q)
QString absoluteFilePath
QStringList children
qsizetype decompress(char *buffer, qsizetype bufferSize) const
bool load(const QString &file)
const uchar * data
static ResourceList * resourceList()
#define RCC_FEATURE_SYMBOL(feature)
Definition qresource.cpp:66
static QRecursiveMutex & resourceMutex()
static auto mappingBoundaries(const void *location, qsizetype size)
QList< QResourceRoot * > ResourceList
Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree, const unsigned char *name, const unsigned char *data)
static QString qt_resource_fixResourceRoot(QString r)
Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree, const unsigned char *name, const unsigned char *data)