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 bool nameMatches(int node, QStringView other) const;
127 short flags(int node) const;
128public:
129 mutable QAtomicInt ref;
130
131 inline QResourceRoot(): tree(nullptr), names(nullptr), payloads(nullptr), version(0) {}
132 inline QResourceRoot(int version, const uchar *t, const uchar *n, const uchar *d) { setSource(version, t, n, d); }
133 virtual ~QResourceRoot() = default;
134 Q_DISABLE_COPY_MOVE(QResourceRoot)
135 int findNode(const QString &path, const QLocale &locale=QLocale()) const;
136 inline bool isContainer(int node) const { return flags(node) & Directory; }
137 QResource::Compression compressionAlgo(int node)
138 {
139 uint compressionFlags = flags(node) & (Compressed | CompressedZstd);
140 if (compressionFlags == Compressed)
141 return QResource::ZlibCompression;
142 if (compressionFlags == CompressedZstd)
143 return QResource::ZstdCompression;
144 return QResource::NoCompression;
145 }
146 const uchar *data(int node, qint64 *size) const;
147 qint64 lastModified(int node) const;
148 QStringList children(int node) const;
149 virtual QString mappingRoot() const { return QString(); }
150 bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
151 inline bool operator==(const QResourceRoot &other) const
152 { return tree == other.tree && names == other.names && payloads == other.payloads && version == other.version; }
153 inline bool operator!=(const QResourceRoot &other) const
154 { return !operator==(other); }
155 enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
156 virtual ResourceRootType type() const { return Resource_Builtin; }
157
158protected:
159 inline void setSource(int v, const uchar *t, const uchar *n, const uchar *d) {
160 tree = t;
161 names = n;
162 payloads = d;
163 version = v;
164 }
165};
166
167static QString cleanPath(const QString &_path)
168{
169 QString path = QDir::cleanPath(_path);
170 // QDir::cleanPath does not remove two trailing slashes under _Windows_
171 // due to support for UNC paths. Remove those manually.
172 if (path.startsWith("//"_L1))
173 path.remove(0, 1);
174 return path;
175}
176} // unnamed namespace
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
792inline bool QResourceRoot::nameMatches(int node, QStringView other) const
793{
794 if (!node) // root
795 return other.isEmpty();
796 const int offset = findOffset(node);
797
798 qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
799 qsizetype name_length = qFromBigEndian<qint16>(names + name_offset);
800 name_offset += 2;
801 name_offset += 4; //jump past hash
802
803 if (other.size() != name_length)
804 return false;
805 const QChar *strData = reinterpret_cast<const QChar *>(names + name_offset);
806 for (qsizetype ch = 0; ch < name_length; ++ch) {
807 if (other.at(ch) != qFromBigEndian<char16_t>(strData + ch))
808 return false;
809 }
810 return true;
811}
812
813int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
814{
815 QString path = _path;
816 {
817 QString root = mappingRoot();
818 if (!root.isEmpty()) {
819 if (root == path) {
820 path = u'/';
821 } else {
822 if (!root.endsWith(u'/'))
823 root += u'/';
824 if (path.size() >= root.size() && path.startsWith(root))
825 path = path.mid(root.size() - 1);
826 if (path.isEmpty())
827 path = u'/';
828 }
829 }
830 }
831#ifdef DEBUG_RESOURCE_MATCH
832 qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
833#endif
834
835 if (path == "/"_L1)
836 return 0;
837
838 // the root node is always first
839 qint32 child_count = qFromBigEndian<qint32>(tree + 6);
840 qint32 child = qFromBigEndian<qint32>(tree + 10);
841
842 // now iterate up the tree
843 int node = -1;
844
845 QStringSplitter splitter(path);
846 while (child_count && splitter.hasNext()) {
847 QStringView segment = splitter.next();
848
849#ifdef DEBUG_RESOURCE_MATCH
850 qDebug() << " CHILDREN" << segment;
851 for (int j = 0; j < child_count; ++j) {
852 qDebug() << " " << child + j << " :: " << name(child + j);
853 }
854#endif
855 const uint h = qt_hash(segment);
856
857 // do the binary search for the hash
858 int l = 0, r = child_count - 1;
859 int sub_node = (l + r + 1) / 2;
860 while (r != l) {
861 const uint sub_node_hash = hash(child + sub_node);
862 if (h == sub_node_hash)
863 break;
864 else if (h < sub_node_hash)
865 r = sub_node - 1;
866 else
867 l = sub_node;
868 sub_node = (l + r + 1) / 2;
869 }
870 sub_node += child;
871
872 // now do the "harder" compares
873 bool found = false;
874 if (hash(sub_node) == h) {
875 while (sub_node > child && hash(sub_node - 1) == h) // backup for collisions
876 --sub_node;
877 for (; sub_node < child + child_count && hash(sub_node) == h;
878 ++sub_node) { // here we go...
879 if (nameMatches(sub_node, segment)) {
880 found = true;
881 int offset = findOffset(sub_node);
882#ifdef DEBUG_RESOURCE_MATCH
883 qDebug() << " TRY" << sub_node << name(sub_node) << offset;
884#endif
885 offset += 4; // jump past name
886
887 const qint16 flags = qFromBigEndian<qint16>(tree + offset);
888 offset += 2;
889
890 if (!splitter.hasNext()) {
891 if (!(flags & Directory)) {
892 const qint16 territory = qFromBigEndian<qint16>(tree + offset);
893 offset += 2;
894
895 const qint16 language = qFromBigEndian<qint16>(tree + offset);
896 offset += 2;
897#ifdef DEBUG_RESOURCE_MATCH
898 qDebug() << " " << "LOCALE" << country << language;
899#endif
900 if (territory == locale.territory() && language == locale.language()) {
901#ifdef DEBUG_RESOURCE_MATCH
902 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
903#endif
904 return sub_node;
905 } else if ((territory == QLocale::AnyTerritory
906 && language == locale.language())
907 || (territory == QLocale::AnyTerritory
908 && language == QLocale::C
909 && node == -1)) {
910 node = sub_node;
911 }
912 continue;
913 } else {
914#ifdef DEBUG_RESOURCE_MATCH
915 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
916#endif
917
918 return sub_node;
919 }
920 }
921
922 if (!(flags & Directory))
923 return -1;
924
925 child_count = qFromBigEndian<qint32>(tree + offset);
926 offset += 4;
927 child = qFromBigEndian<qint32>(tree + offset);
928 break;
929 }
930 }
931 }
932 if (!found)
933 break;
934 }
935#ifdef DEBUG_RESOURCE_MATCH
936 qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
937#endif
938 return node;
939}
940short QResourceRoot::flags(int node) const
941{
942 if (node == -1)
943 return 0;
944 const int offset = findOffset(node) + 4; // jump past name
945 return qFromBigEndian<qint16>(tree + offset);
946}
947const uchar *QResourceRoot::data(int node, qint64 *size) const
948{
949 if (node == -1) {
950 *size = 0;
951 return nullptr;
952 }
953 int offset = findOffset(node) + 4; // jump past name
954
955 const qint16 flags = qFromBigEndian<qint16>(tree + offset);
956 offset += 2;
957
958 offset += 4; // jump past locale
959
960 if (!(flags & Directory)) {
961 const qint32 data_offset = qFromBigEndian<qint32>(tree + offset);
962 const quint32 data_length = qFromBigEndian<quint32>(payloads + data_offset);
963 const uchar *ret = payloads + data_offset + 4;
964 *size = data_length;
965 return ret;
966 }
967 *size = 0;
968 return nullptr;
969}
970
971qint64 QResourceRoot::lastModified(int node) const
972{
973 if (node == -1 || version < 0x02)
974 return 0;
975
976 const int offset = findOffset(node) + 14;
977
978 return qFromBigEndian<qint64>(tree + offset);
979}
980
981QStringList QResourceRoot::children(int node) const
982{
983 if (node == -1)
984 return QStringList();
985 int offset = findOffset(node) + 4; // jump past name
986
987 const qint16 flags = qFromBigEndian<qint16>(tree + offset);
988 offset += 2;
989
990 QStringList ret;
991 if (flags & Directory) {
992 const qint32 child_count = qFromBigEndian<qint32>(tree + offset);
993 offset += 4;
994 const qint32 child_off = qFromBigEndian<qint32>(tree + offset);
995 ret.reserve(child_count);
996 for (int i = child_off; i < child_off + child_count; ++i)
997 ret << name(i);
998 }
999 return ret;
1000}
1001bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
1002{
1003 const QString root = mappingRoot();
1004 if (root.isEmpty())
1005 return false;
1006
1007 QStringSplitter rootIt(root);
1008 QStringSplitter pathIt(path);
1009 while (rootIt.hasNext()) {
1010 if (pathIt.hasNext()) {
1011 if (rootIt.next() != pathIt.next()) // mismatch
1012 return false;
1013 } else {
1014 // end of path, but not of root:
1015 if (match)
1016 *match = rootIt.next().toString();
1017 return true;
1018 }
1019 }
1020 // end of root
1021 return !pathIt.hasNext();
1022}
1023
1024Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
1025 const unsigned char *name, const unsigned char *data)
1026{
1027 if (resourceGlobalData.isDestroyed())
1028 return false;
1029 const auto locker = qt_scoped_lock(resourceMutex());
1031 if (version >= 0x01 && version <= 0x3) {
1032 bool found = false;
1033 QResourceRoot res(version, tree, name, data);
1034 for (int i = 0; i < list->size(); ++i) {
1035 if (*list->at(i) == res) {
1036 found = true;
1037 break;
1038 }
1039 }
1040 if (!found) {
1041 QResourceRoot *root = new QResourceRoot(version, tree, name, data);
1042 root->ref.ref();
1043 list->append(root);
1044 }
1045 return true;
1046 }
1047 return false;
1048}
1049
1050Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
1051 const unsigned char *name, const unsigned char *data)
1052{
1053 if (resourceGlobalData.isDestroyed())
1054 return false;
1055
1056 const auto locker = qt_scoped_lock(resourceMutex());
1057 if (version >= 0x01 && version <= 0x3) {
1058 QResourceRoot res(version, tree, name, data);
1060 for (int i = 0; i < list->size();) {
1061 if (*list->at(i) == res) {
1062 QResourceRoot *root = list->takeAt(i);
1063 if (!root->ref.deref())
1064 delete root;
1065 } else {
1066 ++i;
1067 }
1068 }
1069 return true;
1070 }
1071 return false;
1072}
1073
1074namespace {
1075// run time resource creation
1076class QDynamicBufferResourceRoot : public QResourceRoot
1077{
1078 QString root;
1079 const uchar *buffer;
1080
1081public:
1082 inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
1083 inline ~QDynamicBufferResourceRoot() { }
1084 inline const uchar *mappingBuffer() const { return buffer; }
1085 QString mappingRoot() const override { return root; }
1086 ResourceRootType type() const override { return Resource_Buffer; }
1087
1088 // size == -1 means "unknown"
1089 bool registerSelf(const uchar *b, qsizetype size, const QString &source = {})
1090 {
1091 // 5 int "pointers"
1092 if (size >= 0 && size < 20)
1093 return false;
1094
1095 // setup the data now
1096 int offset = 0;
1097
1098 // magic number
1099 if (b[offset + 0] != 'q' || b[offset + 1] != 'r' || b[offset + 2] != 'e'
1100 || b[offset + 3] != 's') {
1101 return false;
1102 }
1103 offset += 4;
1104
1105 const int version = qFromBigEndian<qint32>(b + offset);
1106 offset += 4;
1107
1108 const int tree_offset = qFromBigEndian<qint32>(b + offset);
1109 offset += 4;
1110
1111 const int data_offset = qFromBigEndian<qint32>(b + offset);
1112 offset += 4;
1113
1114 const int name_offset = qFromBigEndian<qint32>(b + offset);
1115 offset += 4;
1116
1117 quint32 file_flags = 0;
1118 if (version >= 3) {
1119 file_flags = qFromBigEndian<qint32>(b + offset);
1120 offset += 4;
1121 }
1122
1123 // Some sanity checking for sizes. This is _not_ a security measure.
1124 if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
1125 return false;
1126
1127 // And some sanity checking for features
1128 quint32 acceptableFlags = 0;
1129#ifndef QT_NO_COMPRESS
1130 acceptableFlags |= Compressed;
1131#endif
1132 if (QT_CONFIG(zstd))
1133 acceptableFlags |= CompressedZstd;
1134 if (const quint32 unsupportedFlags = file_flags & ~acceptableFlags) {
1135 const QString id = source.isEmpty() ? QString::asprintf("<buffer at %p>", b) : source;
1136 qWarning("QResource: %s uses unsupported compression flags 0x%x (supported: 0x%x)",
1137 qUtf8Printable(id), unsupportedFlags, acceptableFlags);
1138 return false;
1139 }
1140
1141 if (version >= 0x01 && version <= 0x03) {
1142 buffer = b;
1143 setSource(version, b + tree_offset, b + name_offset, b + data_offset);
1144 return true;
1145 }
1146 return false;
1147 }
1148};
1149
1150class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
1151{
1152public:
1153 static uchar *map_sys(QFile &file, qint64 base, qsizetype size);
1154 static void unmap_sys(void *base, qsizetype size);
1155
1156private:
1157 QString fileName;
1158 // for mmap'ed files, this is what needs to be unmapped.
1159 uchar *unmapPointer;
1160 qsizetype unmapLength;
1161
1162public:
1163 QDynamicFileResourceRoot(const QString &_root)
1164 : QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
1165 { }
1166 ~QDynamicFileResourceRoot() {
1167 if (wasMemoryMapped())
1168 unmap_sys(unmapPointer, unmapLength);
1169 else
1170 delete[] mappingBuffer();
1171 }
1172 QString mappingFile() const { return fileName; }
1173 ResourceRootType type() const override { return Resource_File; }
1174 bool wasMemoryMapped() const { return unmapPointer; }
1175
1176 bool registerSelf(const QString &f);
1177};
1178} // unnamed namespace
1179
1180#ifndef MAP_FILE
1181# define MAP_FILE 0
1182#endif
1183#ifndef MAP_FAILED
1184# define MAP_FAILED reinterpret_cast<void *>(-1)
1185#endif
1186
1187void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size)
1188{
1189#if defined(QT_USE_MMAP)
1190 munmap(base, size);
1191#elif defined(Q_OS_WIN)
1192 Q_UNUSED(size)
1193 UnmapViewOfFile(reinterpret_cast<void *>(base));
1194#endif
1195}
1196
1197// Note: caller must ensure \a offset and \a size are acceptable to the OS.
1198uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size)
1199{
1200 Q_ASSERT(file.isOpen());
1201 void *ptr = nullptr;
1202 if (size < 0)
1203 size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)());
1204 int fd = file.handle();
1205 if (fd < 0)
1206 return nullptr;
1207 // We don't use QFile::map() here because we want to dispose of the QFile object
1208#if defined(QT_USE_MMAP)
1209 int protection = PROT_READ; // read-only memory
1210 int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
1211 ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset);
1212 if (ptr == MAP_FAILED)
1213 ptr = nullptr;
1214#elif defined(Q_OS_WIN)
1215 HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
1216 if (fileHandle != INVALID_HANDLE_VALUE) {
1217 HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0);
1218 if (mapHandle) {
1219 ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size);
1220 CloseHandle(mapHandle);
1221 }
1222 }
1223#endif // QT_USE_MMAP
1224 return static_cast<uchar *>(ptr);
1225}
1226
1227bool QDynamicFileResourceRoot::registerSelf(const QString &f)
1228{
1229 QFile file(f);
1230 if (!file.open(QIODevice::ReadOnly))
1231 return false;
1232
1233 qint64 data_len = file.size();
1234 if (data_len > std::numeric_limits<qsizetype>::max())
1235 return false;
1236
1237 uchar *data = map_sys(file, 0, data_len);
1238 bool fromMM = !!data;
1239
1240 if (!fromMM) {
1241 bool ok = false;
1242 data = new uchar[data_len];
1243 ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
1244 if (!ok) {
1245 delete[] data;
1246 data = nullptr;
1247 data_len = 0;
1248 return false;
1249 }
1250 }
1251 if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len, f)) {
1252 if (fromMM) {
1253 unmapPointer = data;
1254 unmapLength = data_len;
1255 }
1256 fileName = f;
1257 return true;
1258 }
1259 return false;
1260}
1261
1262static QString qt_resource_fixResourceRoot(QString r)
1263{
1264 if (!r.isEmpty()) {
1265 if (r.startsWith(u':'))
1266 r = r.mid(1);
1267 if (!r.isEmpty())
1268 r = QDir::cleanPath(r);
1269 }
1270 return r;
1271}
1272
1273/*!
1274 \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
1275
1276 Registers the resource with the given \a rccFileName at the location in the
1277 resource tree specified by \a mapRoot, and returns \c true if the file is
1278 successfully opened; otherwise returns \c false.
1279
1280 \sa unregisterResource()
1281*/
1282
1283bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
1284{
1285 QString r = qt_resource_fixResourceRoot(resourceRoot);
1286 if (!r.isEmpty() && r[0] != u'/') {
1287 qWarning("QDir::registerResource: Registering a resource [%ls] must be rooted in an "
1288 "absolute path (start with /) [%ls]",
1289 qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
1290 return false;
1291 }
1292
1293 QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
1294 if (root->registerSelf(rccFilename)) {
1295 root->ref.ref();
1296 const auto locker = qt_scoped_lock(resourceMutex());
1297 resourceList()->append(root);
1298 return true;
1299 }
1300 delete root;
1301 return false;
1302}
1303
1304/*!
1305 \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
1306
1307 Unregisters the resource with the given \a rccFileName at the location in
1308 the resource tree specified by \a mapRoot, and returns \c true if the
1309 resource is successfully unloaded and no references exist for the
1310 resource; otherwise returns \c false.
1311
1312 \sa registerResource()
1313*/
1314
1315bool QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
1316{
1317 QString r = qt_resource_fixResourceRoot(resourceRoot);
1318
1319 const auto locker = qt_scoped_lock(resourceMutex());
1320 ResourceList *list = resourceList();
1321 for (int i = 0; i < list->size(); ++i) {
1322 QResourceRoot *res = list->at(i);
1323 if (res->type() == QResourceRoot::Resource_File) {
1324 QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot *>(res);
1325 if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
1326 list->removeAt(i);
1327 if (!root->ref.deref()) {
1328 delete root;
1329 return true;
1330 }
1331 return false;
1332 }
1333 }
1334 }
1335 return false;
1336}
1337
1338/*!
1339 \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
1340 \since 4.3
1341
1342 Registers the resource with the given \a rccData at the location in the
1343 resource tree specified by \a mapRoot, and returns \c true if the file is
1344 successfully opened; otherwise returns \c false.
1345
1346 \warning The data must remain valid throughout the life of any QFile
1347 that may reference the resource data.
1348
1349 \sa unregisterResource()
1350*/
1351
1352bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
1353{
1354 QString r = qt_resource_fixResourceRoot(resourceRoot);
1355 if (!r.isEmpty() && r[0] != u'/') {
1356 qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an "
1357 "absolute path (start with /) [%ls]",
1358 rccData, qUtf16Printable(resourceRoot));
1359 return false;
1360 }
1361
1362 QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
1363 if (root->registerSelf(rccData, -1)) {
1364 root->ref.ref();
1365 const auto locker = qt_scoped_lock(resourceMutex());
1366 resourceList()->append(root);
1367 return true;
1368 }
1369 delete root;
1370 return false;
1371}
1372
1373/*!
1374 \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
1375 \since 4.3
1376
1377 Unregisters the resource with the given \a rccData at the location in the
1378 resource tree specified by \a mapRoot, and returns \c true if the resource is
1379 successfully unloaded and no references exist into the resource; otherwise returns \c false.
1380
1381 \sa registerResource()
1382*/
1383
1384bool QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
1385{
1386 QString r = qt_resource_fixResourceRoot(resourceRoot);
1387
1388 const auto locker = qt_scoped_lock(resourceMutex());
1389 ResourceList *list = resourceList();
1390 for (int i = 0; i < list->size(); ++i) {
1391 QResourceRoot *res = list->at(i);
1392 if (res->type() == QResourceRoot::Resource_Buffer) {
1393 QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot *>(res);
1394 if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
1395 list->removeAt(i);
1396 if (!root->ref.deref()) {
1397 delete root;
1398 return true;
1399 }
1400 return false;
1401 }
1402 }
1403 }
1404 return false;
1405}
1406
1407#if !defined(QT_BOOTSTRAPPED)
1408// resource engine
1410{
1411protected:
1412 Q_DECLARE_PUBLIC(QResourceFileEngine)
1413private:
1415 bool unmap(uchar *ptr);
1416 void uncompress() const;
1417 void mapUncompressed();
1418 bool mapUncompressed_sys();
1419 void unmapUncompressed_sys();
1420 qint64 offset = 0;
1421 QResource resource;
1422 mutable QByteArray uncompressed;
1423 bool mustUnmap = false;
1424
1425 // minimum size for which we'll try to re-open ourselves in mapUncompressed()
1426 static constexpr qsizetype RemapCompressedThreshold = 16384;
1427protected:
1428 QResourceFileEnginePrivate(QAbstractFileEngine *q) :
1430
1432 {
1433 if (mustUnmap)
1434 unmapUncompressed_sys();
1435 }
1436};
1437
1439{
1440 return true;
1441}
1442
1443QResourceFileEngine::QResourceFileEngine(const QString &file) :
1444 QAbstractFileEngine(*new QResourceFileEnginePrivate(this))
1445{
1446 Q_D(QResourceFileEngine);
1447 d->resource.setFileName(file);
1448}
1449
1453
1454void QResourceFileEngine::setFileName(const QString &file)
1455{
1456 Q_D(QResourceFileEngine);
1457 d->resource.setFileName(file);
1458}
1459
1460bool QResourceFileEngine::open(QIODevice::OpenMode flags,
1461 std::optional<QFile::Permissions> permissions)
1462{
1463 Q_UNUSED(permissions);
1464
1465 Q_D(QResourceFileEngine);
1466 if (d->resource.fileName().isEmpty()) {
1467 qWarning("QResourceFileEngine::open: Missing file name");
1468 return false;
1469 }
1470 if (flags & QIODevice::WriteOnly)
1471 return false;
1472 if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
1473 d->uncompress();
1474 if (d->uncompressed.isNull()) {
1475 d->errorString = QSystemError::stdString(EIO);
1476 return false;
1477 }
1478 }
1479 if (!d->resource.isValid()) {
1480 d->errorString = QSystemError::stdString(ENOENT);
1481 return false;
1482 }
1483 return true;
1484}
1485
1487{
1488 Q_D(QResourceFileEngine);
1489 d->offset = 0;
1490 return true;
1491}
1492
1494{
1495 return true;
1496}
1497
1498qint64 QResourceFileEngine::read(char *data, qint64 len)
1499{
1500 Q_D(QResourceFileEngine);
1501 if (len > size() - d->offset)
1502 len = size() - d->offset;
1503 if (len <= 0)
1504 return 0;
1505 if (!d->uncompressed.isNull())
1506 memcpy(data, d->uncompressed.constData() + d->offset, len);
1507 else
1508 memcpy(data, d->resource.data() + d->offset, len);
1509 d->offset += len;
1510 return len;
1511}
1512
1514{
1515 Q_D(const QResourceFileEngine);
1516 return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
1517}
1518
1520{
1521 Q_D(const QResourceFileEngine);
1522 return d->offset;
1523}
1524
1526{
1527 Q_D(const QResourceFileEngine);
1528 if (!d->resource.isValid())
1529 return true;
1530 return d->offset == size();
1531}
1532
1533bool QResourceFileEngine::seek(qint64 pos)
1534{
1535 Q_D(QResourceFileEngine);
1536 if (!d->resource.isValid())
1537 return false;
1538
1539 if (d->offset > size())
1540 return false;
1541 d->offset = pos;
1542 return true;
1543}
1544
1545QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
1546{
1547 Q_D(const QResourceFileEngine);
1548 QAbstractFileEngine::FileFlags ret;
1549 if (!d->resource.isValid())
1550 return ret;
1551
1552 if (type & PermsMask)
1553 ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm | ReadUserPerm | ReadGroupPerm
1554 | ReadOtherPerm);
1555 if (type & TypesMask) {
1556 if (d->resource.isDir())
1557 ret |= DirectoryType;
1558 else
1559 ret |= FileType;
1560 }
1561 if (type & FlagsMask) {
1562 ret |= ExistsFlag;
1563 if (d->resource.absoluteFilePath() == ":/"_L1)
1564 ret |= RootFlag;
1565 }
1566 return ret;
1567}
1568
1569QString QResourceFileEngine::fileName(FileName file) const
1570{
1571 Q_D(const QResourceFileEngine);
1572 if (file == BaseName) {
1573 const qsizetype slash = d->resource.fileName().lastIndexOf(u'/');
1574 if (slash == -1)
1575 return d->resource.fileName();
1576 return d->resource.fileName().mid(slash + 1);
1577 } else if (file == PathName || file == AbsolutePathName) {
1578 const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
1579 : d->resource.fileName();
1580 const qsizetype slash = path.lastIndexOf(u'/');
1581 if (slash == -1)
1582 return ":"_L1;
1583 else if (slash <= 1)
1584 return ":/"_L1;
1585 return path.left(slash);
1586
1587 } else if (file == CanonicalName || file == CanonicalPathName) {
1588 const QString absoluteFilePath = d->resource.absoluteFilePath();
1589 if (file == CanonicalPathName) {
1590 const qsizetype slash = absoluteFilePath.lastIndexOf(u'/');
1591 if (slash != -1)
1592 return absoluteFilePath.left(slash);
1593 }
1594 return absoluteFilePath;
1595 }
1596 return d->resource.fileName();
1597}
1598
1600{
1601 static const uint nobodyID = static_cast<uint>(-2);
1602 return nobodyID;
1603}
1604
1605QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const
1606{
1607 Q_D(const QResourceFileEngine);
1608 if (time == QFile::FileModificationTime)
1609 return d->resource.lastModified();
1610 return QDateTime();
1611}
1612
1613/*!
1614 \internal
1615*/
1616QAbstractFileEngine::IteratorUniquePtr
1617QResourceFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
1618 const QStringList &filterNames)
1619{
1620 return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
1621}
1622
1623bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
1624{
1625 Q_D(QResourceFileEngine);
1626 if (extension == MapExtension) {
1627 const auto *options = static_cast<const MapExtensionOption *>(option);
1628 auto *returnValue = static_cast<MapExtensionReturn *>(output);
1629 returnValue->address = d->map(options->offset, options->size, options->flags);
1630 return (returnValue->address != nullptr);
1631 }
1632 if (extension == UnMapExtension) {
1633 const auto *options = static_cast<const UnMapExtensionOption *>(option);
1634 return d->unmap(options->address);
1635 }
1636 return false;
1637}
1638
1639bool QResourceFileEngine::supportsExtension(Extension extension) const
1640{
1641 return (extension == UnMapExtension || extension == MapExtension);
1642}
1643
1644uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
1645{
1646 Q_Q(QResourceFileEngine);
1647 Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression
1648 || !uncompressed.isNull(), "QFile::map()",
1649 "open() should have uncompressed compressed resources");
1650
1651 qint64 max = resource.uncompressedSize();
1652 qint64 end;
1653 if (offset < 0 || size <= 0 || !resource.isValid() ||
1654 qAddOverflow(offset, size, &end) || end > max) {
1655 q->setError(QFile::UnspecifiedError, QString());
1656 return nullptr;
1657 }
1658
1659 const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin());
1660 if (!uncompressed.isNull())
1661 return const_cast<uchar *>(address) + offset;
1662
1663 // resource was not compressed
1664 address = resource.data();
1665 if (flags & QFile::MapPrivateOption) {
1666 // We need to provide read-write memory
1667 mapUncompressed();
1668 address = reinterpret_cast<const uchar *>(uncompressed.constData());
1669 }
1670
1671 return const_cast<uchar *>(address) + offset;
1672}
1673
1674bool QResourceFileEnginePrivate::unmap(uchar *ptr)
1675{
1676 Q_UNUSED(ptr);
1677 return true;
1678}
1679
1680void QResourceFileEnginePrivate::uncompress() const
1681{
1682 if (resource.compressionAlgorithm() == QResource::NoCompression
1683 || !uncompressed.isEmpty() || resource.size() == 0)
1684 return; // nothing to do
1685 uncompressed = resource.uncompressedData();
1686}
1687
1688void QResourceFileEnginePrivate::mapUncompressed()
1689{
1690 Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression);
1691 if (!uncompressed.isNull())
1692 return; // nothing to do
1693
1694 if (resource.uncompressedSize() >= RemapCompressedThreshold) {
1695 if (mapUncompressed_sys())
1696 return;
1697 }
1698
1699 uncompressed = resource.uncompressedData();
1700 uncompressed.detach();
1701}
1702
1703#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
1704inline bool QResourcePrivate::mayRemapData(const QResource &resource)
1705{
1706 auto d = resource.d_func();
1707
1708 // assumptions from load():
1709 // - d->related is not empty
1710 // - the first item in d->related is the one with our data
1711 // by current construction, it's also the only item
1712 const QResourceRoot *root = d->related.at(0);
1713
1714 switch (root->type()) {
1715 case QResourceRoot::Resource_Builtin:
1716 return true; // always acceptable, memory is read-only
1717 case QResourceRoot::Resource_Buffer:
1718 return false; // never acceptable, memory is heap
1719 case QResourceRoot::Resource_File:
1720 break;
1721 }
1722
1723 auto df = static_cast<const QDynamicFileResourceRoot *>(root);
1724 return df->wasMemoryMapped();
1725}
1726#endif
1727
1728// Returns the page boundaries of where \a location is located in memory.
1729static auto mappingBoundaries(const void *location, qsizetype size)
1730{
1731#ifdef Q_OS_WIN
1732 auto getpagesize = [] {
1733 SYSTEM_INFO sysinfo;
1734 ::GetSystemInfo(&sysinfo);
1735 return sysinfo.dwAllocationGranularity;
1736 };
1737#endif
1738 struct R {
1739 void *begin;
1740 qsizetype size;
1741 qptrdiff offset;
1742 } r;
1743
1744 const quintptr pageMask = getpagesize() - 1;
1745 quintptr data = quintptr(location);
1746 quintptr begin = data & ~pageMask;
1747 quintptr end = (data + size + pageMask) & ~pageMask;
1748 r.begin = reinterpret_cast<void *>(begin);
1749 r.size = end - begin;
1750 r.offset = data & pageMask;
1751 return r;
1752}
1753
1754bool QResourceFileEnginePrivate::mapUncompressed_sys()
1755{
1756 auto r = mappingBoundaries(resource.data(), resource.uncompressedSize());
1757 void *ptr = nullptr;
1758
1759#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
1760 // Use MREMAP_MAYMOVE to tell the kernel to give us a new address and use
1761 // MREMAP_DONTUNMAP (supported since kernel 5.7) to request that it create
1762 // a new mapping of the same pages, instead of moving. We can only do that
1763 // for pages that are read-only, otherwise the kernel replaces the source
1764 // with pages full of nulls.
1765 if (!QResourcePrivate::mayRemapData(resource))
1766 return false;
1767
1768 ptr = mremap(r.begin, r.size, r.size, MREMAP_MAYMOVE | MREMAP_DONTUNMAP);
1769 if (ptr == MAP_FAILED)
1770 return false;
1771
1772 // Allow writing, which the documentation says we allow. This is safe
1773 // because MREMAP_DONTUNMAP only works for private mappings.
1774 if (mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
1775 munmap(ptr, r.size);
1776 return false;
1777 }
1778#elif defined(Q_OS_DARWIN)
1779 mach_port_t self = mach_task_self();
1780 vm_address_t addr = 0;
1781 vm_address_t mask = 0;
1782 bool anywhere = true;
1783 bool copy = true;
1784 vm_prot_t cur_prot = VM_PROT_READ | VM_PROT_WRITE;
1785 vm_prot_t max_prot = VM_PROT_ALL;
1786 kern_return_t res = vm_remap(self, &addr, r.size, mask, anywhere,
1787 self, vm_address_t(r.begin), copy, &cur_prot,
1788 &max_prot, VM_INHERIT_DEFAULT);
1789 if (res != KERN_SUCCESS)
1790 return false;
1791
1792 ptr = reinterpret_cast<void *>(addr);
1793 if ((max_prot & VM_PROT_WRITE) == 0 || mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
1794 munmap(ptr, r.size);
1795 return false;
1796 }
1797#endif
1798
1799 if (!ptr)
1800 return false;
1801 const char *newdata = static_cast<char *>(ptr) + r.offset;
1802 uncompressed = QByteArray::fromRawData(newdata, resource.uncompressedSize());
1803 mustUnmap = true;
1804 return true;
1805}
1806
1807void QResourceFileEnginePrivate::unmapUncompressed_sys()
1808{
1809 auto r = mappingBoundaries(uncompressed.constBegin(), uncompressed.size());
1810 QDynamicFileResourceRoot::unmap_sys(r.begin, r.size);
1811}
1812
1813#endif // !defined(QT_BOOTSTRAPPED)
1814
1815QT_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)