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