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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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