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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/corelib/io/qresource.cpp