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