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